tcElevationOptimization::loadSnapShot: simplify reliability expression
[tecorrec.git] / geo / tcElevationOptimization.cpp
blobe57e5f75a609f7493970f3f133f8017e9cb32dfb
1 /***************************************************************************
2 * This file is part of Tecorrec. *
3 * Copyright 2008 James Hogan <james@albanarts.com> *
4 * *
5 * Tecorrec is free software: you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation, either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * Tecorrec is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with Tecorrec. If not, write to the Free Software Foundation, *
17 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
18 ***************************************************************************/
20 /**
21 * @file tcElevationOptimization.cpp
22 * @brief Optimized elevation.
25 #include "tcElevationOptimization.h"
26 #include "tcChannelConfigWidget.h"
27 #include "tcSrtmModel.h"
28 #include "tcShadowClassifyingData.h"
29 #include <SplineDefinitions.h>
31 #include <qwt_plot.h>
32 #include <qwt_plot_curve.h>
34 #include <QObject>
35 #include <QWidget>
36 #include <QVBoxLayout>
37 #include <QHBoxLayout>
38 #include <QGridLayout>
39 #include <QPushButton>
40 #include <QSpinBox>
41 #include <QLabel>
42 #include <QSlider>
43 #include <QMessageBox>
44 #include <QFileDialog>
45 #include <QTextStream>
46 #include <QThread>
49 * Private types
52 /// Worker thread class.
53 class tcElevationOptimization::WorkerThread : public QThread
55 public:
57 * Constructors + destructor
60 /// Constructor.
61 WorkerThread(tcElevationOptimization* self)
62 : QThread()
63 , m_this(self)
67 /// Destructor.
68 virtual ~WorkerThread()
72 protected:
73 /// Reimplemented.
74 virtual void run()
76 m_this->optimize();
78 m_this->m_stopProcessing = false;
79 m_this->m_btnOptimize->setText(tr("Optimize Iteratively"));
80 m_this->m_btnOptimize->update();
81 m_this->m_processingMutex.unlock();
84 private:
87 * Variables
90 /// Main optimization object.
91 tcElevationOptimization* m_this;
95 * Constructors + destructor
98 /// Primary constructor.
99 tcElevationOptimization::tcElevationOptimization(const QList<tcShadowClassifyingData*>& datasets, tcSrtmModel* dem, tcGeoImageData* imagery)
100 : tcChannelDem(dem, imagery,
101 tr("Elevation Optimization"),
102 tr("Optimizes elevation using image data."),
103 false, true)
104 , m_numDatasets(datasets.size())
105 , m_datasets(new tcGeoImageData*[m_numDatasets])
106 , m_texToDatasetTex(new tcAffineTransform2<double>[m_numDatasets])
107 , m_shadowChannels(new tcChannel*[m_numDatasets])
108 , m_shadingChannels(new tcChannel*[m_numDatasets])
109 , m_elevationData(0)
110 , m_data(new tcTypedPixelData<PerDatasetPixelCache>*[m_numDatasets])
111 , m_datum(0)
112 , m_shadowData(new Reference<tcPixelData<float> >[m_numDatasets])
113 , m_shadingData(new Reference<tcPixelData<float> >[m_numDatasets])
114 , m_voidsOnly(false)
115 , m_loadingFromDem(false)
116 , m_thread(0)
117 , m_processingMutex()
118 , m_stopProcessing(false)
119 , m_btnOptimize(0)
120 , m_configWidget(0)
121 , m_spinIterations(0)
122 , m_plot(new QwtPlot())
123 , m_stdDevCurve(new QwtPlotCurve())
124 , m_stdDevVoidCurve(new QwtPlotCurve())
125 , m_stdDevNonVoidCurve(new QwtPlotCurve())
126 , m_snapshotFile()
127 , m_snapshotReadFile()
128 , m_snapshotReading(false)
129 , m_snapshotSlider(0)
130 , m_snapshotCounter(0)
132 m_plot->setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
133 m_plot->setTitle("Standard Deviations");
134 connect(this, SIGNAL(replotStdDeviations()), m_plot, SLOT(replot()), Qt::QueuedConnection);
135 connect(this, SIGNAL(invalidateAndRedraw()), this, SLOT(invalidateAndRedrawSlot()), Qt::BlockingQueuedConnection);
136 m_stdDevVoidCurve->setPen(QPen(Qt::black));
137 m_stdDevVoidCurve->attach(m_plot);
138 m_stdDevNonVoidCurve->setPen(QPen(Qt::green));
139 m_stdDevNonVoidCurve->attach(m_plot);
140 m_stdDevCurve->setPen(QPen(Qt::blue));
141 m_stdDevCurve->attach(m_plot);
142 for (int i = 0; i < m_numDatasets; ++i)
144 tcShadowClassifyingData* landsat = datasets[i];
145 m_datasets[i] = landsat;
146 m_texToDatasetTex[i] = imagery->texToGeo() * landsat->geoToTex();
147 m_shadowChannels[i] = landsat->shadowClassification();
148 if (0 != m_shadowChannels[i])
150 m_shadowChannels[i]->addDerivitive(this);
152 m_shadingChannels[i] = landsat->shading();
153 if (0 != m_shadingChannels[i])
155 m_shadingChannels[i]->addDerivitive(this);
157 m_data[i] = 0;
161 /// Destructor.
162 tcElevationOptimization::~tcElevationOptimization()
164 if (!m_processingMutex.tryLock())
166 m_stopProcessing = true;
167 m_thread->wait();
169 delete m_thread;
170 delete m_plot;
171 delete [] m_datasets;
172 delete [] m_texToDatasetTex;
173 for (int i = 0; i < m_numDatasets; ++i)
175 if (0 != m_shadowChannels[i])
177 m_shadowChannels[i]->removeDerivitive(this);
179 if (0 != m_shadingChannels[i])
181 m_shadingChannels[i]->removeDerivitive(this);
183 delete m_data[i];
185 delete [] m_shadowChannels;
186 delete [] m_shadingChannels;
187 delete m_elevationData;
188 delete [] m_data;
189 delete m_datum;
190 delete [] m_shadowData;
191 delete [] m_shadingData;
192 delete m_configWidget;
196 * Main image interface
199 tcChannelConfigWidget* tcElevationOptimization::configWidget()
201 if (0 == m_configWidget)
203 m_configWidget = new tcChannelConfigWidget();
204 m_configWidget->setWindowTitle(tr("%1 Configuration").arg(name()));
206 QVBoxLayout* layout = new QVBoxLayout(m_configWidget);
208 QLabel* stats = new QLabel(m_configWidget);
209 layout->addWidget(stats);
210 connect(this, SIGNAL(statistics(const QString&)), stats, SLOT(setText(const QString&)));
212 // Row of optimisation controls
213 QWidget* buttons1 = new QWidget(m_configWidget);
214 layout->addWidget(buttons1);
215 QHBoxLayout* buttons1Layout = new QHBoxLayout(buttons1);
217 // reset DEM (sets all corrections to the original (interpolated) values
218 QPushButton* btnResetDem = new QPushButton(tr("Reset DEM"), m_configWidget);
219 buttons1Layout->addWidget(btnResetDem);
220 btnResetDem->setToolTip(tr("Reset DEM corrected values to interpolated values"));
221 connect(btnResetDem, SIGNAL(clicked(bool)), this, SLOT(resetDem()));
223 // load base from DEM (loads DEM elevations into elevation buffer)
224 QPushButton* btnLoadDem = new QPushButton(tr("Load DEM"), m_configWidget);
225 buttons1Layout->addWidget(btnLoadDem);
226 btnLoadDem->setToolTip(tr("Load corrected values from DEM into elevation field"));
227 connect(btnLoadDem, SIGNAL(clicked(bool)), this, SLOT(loadFromDem()));
229 // number of iterations (select number of iterations to perform)
230 m_spinIterations = new QSpinBox(m_configWidget);
231 buttons1Layout->addWidget(m_spinIterations);
232 m_spinIterations->setToolTip(tr("Number of iterations of optimization to perform"));
233 m_spinIterations->setRange(1, 1000);
234 m_spinIterations->setValue(10);
236 // optimize (iterates on elevation buffer and write back to DEM at intervals and end)
237 m_btnOptimize = new QPushButton(tr("Optimize Iteratively"), m_configWidget);
238 buttons1Layout->addWidget(m_btnOptimize);
239 m_btnOptimize->setToolTip(tr("Iteratively optimize elevation field and write results back to DEM"));
240 connect(m_btnOptimize, SIGNAL(clicked(bool)), this, SLOT(startStopOptimize()));
242 // Another row of optimisation controls
243 QWidget* buttons2 = new QWidget(m_configWidget);
244 layout->addWidget(buttons2);
245 QHBoxLayout* buttons2Layout = new QHBoxLayout(buttons2);
247 // apply hard constraints, adjusting reliabilities
248 QPushButton* btnHardConstrain = new QPushButton(tr("Hard Constrain"), m_configWidget);
249 buttons2Layout->addWidget(btnHardConstrain);
250 btnLoadDem->setToolTip(tr("Apply hard shadow constraints"));
251 connect(btnHardConstrain, SIGNAL(clicked(bool)), this, SLOT(applyHardConstraints()));
253 // bilinear interpolation of voids
254 QPushButton* btnBilinear = new QPushButton(tr("Bilinear"), m_configWidget);
255 buttons2Layout->addWidget(btnBilinear);
256 btnLoadDem->setToolTip(tr("Void-fill using bilinear interpolation"));
257 connect(btnBilinear, SIGNAL(clicked(bool)), this, SLOT(voidFillBilinear()));
259 // bicubic interpolation of voids
260 QPushButton* btnBicubic = new QPushButton(tr("Bicubic"), m_configWidget);
261 buttons2Layout->addWidget(btnBicubic);
262 btnLoadDem->setToolTip(tr("Void-fill using bicubic interpolation"));
263 connect(btnBicubic, SIGNAL(clicked(bool)), this, SLOT(voidFillBicubic()));
265 // Another row of controls, for graphs
266 QWidget* buttons3 = new QWidget(m_configWidget);
267 layout->addWidget(buttons3);
268 QHBoxLayout* buttons3Layout = new QHBoxLayout(buttons3);
270 // Show standard deviation curve
271 QPushButton* btnStdDev = new QPushButton(tr("Std Dev"), m_configWidget);
272 buttons3Layout->addWidget(btnStdDev);
273 btnStdDev->setToolTip(tr("Show standard deviation graphs"));
274 connect(btnStdDev, SIGNAL(clicked(bool)), m_plot, SLOT(show()));
276 // Clear standard deviation
277 QPushButton* btnStdDevClear = new QPushButton(tr("Clear"), m_configWidget);
278 buttons3Layout->addWidget(btnStdDevClear);
279 btnStdDevClear->setToolTip(tr("Clear standard deviation graphs"));
280 connect(btnStdDevClear, SIGNAL(clicked(bool)), this, SLOT(clearStdDeviations()));
282 // Sample standard deviation
283 QPushButton* btnStdDevSample = new QPushButton(tr("Sample"), m_configWidget);
284 buttons3Layout->addWidget(btnStdDevSample);
285 btnStdDevSample->setToolTip(tr("Sample standard deviation"));
286 connect(btnStdDevSample, SIGNAL(clicked(bool)), this, SLOT(sampleStdDeviations()));
288 // Save standard deviation
289 QPushButton* btnStdDevSave = new QPushButton(tr("Save"), m_configWidget);
290 buttons3Layout->addWidget(btnStdDevSave);
291 btnStdDevSave->setToolTip(tr("Save standard deviation samples to a text file for external processing"));
292 connect(btnStdDevSave, SIGNAL(clicked(bool)), this, SLOT(saveStdDeviations()));
294 // Another row of controls, for terrain snapshotting
295 QWidget* buttons4 = new QWidget(m_configWidget);
296 layout->addWidget(buttons4);
297 QHBoxLayout* buttons4Layout = new QHBoxLayout(buttons4);
299 // Initialise snapshotting
300 QPushButton* btnInitSnapshotting = new QPushButton(tr("Start snapshotting"), m_configWidget);
301 buttons4Layout->addWidget(btnInitSnapshotting);
302 btnInitSnapshotting->setToolTip(tr("Start snapshotting the terrain at intervals for later analysis"));
303 connect(btnInitSnapshotting, SIGNAL(clicked(bool)), this, SLOT(initSnapShotting()));
305 // Load snapshots
306 QPushButton* btnLoadSnapshots = new QPushButton(tr("Load snapshots"), m_configWidget);
307 buttons4Layout->addWidget(btnLoadSnapshots);
308 btnLoadSnapshots->setToolTip(tr("Load a sequence of snapshots for processing"));
309 connect(btnLoadSnapshots, SIGNAL(clicked(bool)), this, SLOT(loadSnapShots()));
311 // Samples resolution
312 m_crossResolution = new QSpinBox(m_configWidget);
313 buttons4Layout->addWidget(m_crossResolution);
314 m_crossResolution->setToolTip(tr("Cross sectioning resolution"));
315 m_crossResolution->setRange(64, 1024);
316 m_crossResolution->setValue(1024);
318 // Frequency of samples
319 m_crossFrequency = new QSpinBox(m_configWidget);
320 buttons4Layout->addWidget(m_crossFrequency);
321 m_crossFrequency->setToolTip(tr("Cross sectioning frequency"));
322 m_crossFrequency->setRange(1, 10);
323 m_crossFrequency->setValue(1);
325 // Button to save cross sectioning data
326 QPushButton* btnCrossSectionSave = new QPushButton(tr("Save cross section data"), m_configWidget);
327 buttons4Layout->addWidget(btnCrossSectionSave);
328 connect(btnCrossSectionSave, SIGNAL(clicked(bool)), this, SLOT(saveCrossSections()));
330 // Current frame
331 m_snapshotCounter = new QLabel("0", m_configWidget);
332 buttons4Layout->addWidget(m_snapshotCounter);
334 // Snapshot slider
335 m_snapshotSlider = new QSlider(Qt::Horizontal, m_configWidget);
336 layout->addWidget(m_snapshotSlider);
337 m_snapshotSlider->setEnabled(false);
338 connect(m_snapshotSlider, SIGNAL(sliderMoved(int)), this, SLOT(loadSnapShot(int)));
340 // Rows of weight sliders
341 QString weightNames[WeightsMax] = {
342 tr("Variance"),
343 tr("Roughness"),
344 tr("Steepness"),
345 tr("Lit facing sun"),
346 tr("Shape from shading"),
347 tr("Photometric Stereo"),
348 tr("Below shadow line"),
349 tr("Transition elevation (top)"),
350 tr("Transition elevation (bottom)"),
351 tr("Transition slope"),
352 tr("Transition curving down"),
353 tr("Elevation Step"),
354 tr("Range")
356 float defaults[WeightsMax][3] = {
357 {0.0f, 10.0f, 1.0f}, // variance
358 {0.0f, 5.0f, 0.1f}, // roughenss
359 {0.0f, 5.0f, 0.1f}, // steepness
360 {0.0f, 1000.0f, 255.0f}, // lit facing
361 {0.0f, 100.0f, 0.0f}, // shading
362 {0.0f, 100.0f, 0.0f}, // photometric stereo
363 {0.0f, 10.0f, 1.0f}, // belowShadowLine
364 {0.0f, 10.0f, 1.0f}, // transitionElevTop
365 {0.0f, 10.0f, 1.0f}, // transitionElevBottom
366 {0.0f, 1000.0f, 100.0f}, // transitionTangential
367 {0.0f, 10.0f, 1.0f}, // transitionCurvingDown
368 {0.0f, 20.0f, 4.0f}, // elevation step
369 {0.0f, 4.0f, 0.0f}, // range
371 QWidget* sliders = new QWidget(m_configWidget);
372 layout->addWidget(sliders);
373 QGridLayout* slidersLayout = new QGridLayout(sliders);
375 int i = 0;
376 for (; i < WeightsMax; ++i)
378 QLabel* label = new QLabel(weightNames[i], sliders);
379 slidersLayout->addWidget(label, i, 0);
381 int steps = 1000;
382 m_weightLimits[0][i] = defaults[i][0];
383 m_weightLimits[1][i] = (defaults[i][1] - defaults[i][0])/steps;
384 int val = (int)((defaults[i][2] - m_weightLimits[0][i]) / m_weightLimits[1][i]);
386 for (int sl = 0; sl < 2; ++sl)
388 m_weightSliders[sl][i] = new QSlider(Qt::Horizontal, sliders);
389 slidersLayout->addWidget(m_weightSliders[sl][i], i, sl+1);
390 m_weightSliders[sl][i]->setRange(0, steps);
391 m_weightSliders[sl][i]->setValue(val);
392 connect(m_weightSliders[sl][i], SIGNAL(sliderMoved(int)), this, SLOT(updateWeightLabels()));
393 connect(m_weightSliders[sl][i], SIGNAL(valueChanged(int)), this, SLOT(updateWeightLabels()));
395 m_weightLabels[i] = new QLabel("-", sliders);
396 slidersLayout->addWidget(m_weightLabels[i], i, 3);
398 updateWeightLabels();
400 // Another row of controls, for weight saving, loading
401 QWidget* buttons5 = new QWidget(m_configWidget);
402 layout->addWidget(buttons5);
403 QHBoxLayout* buttons5Layout = new QHBoxLayout(buttons5);
405 // Save weights
406 QPushButton* btnSaveWeights = new QPushButton(tr("Save weights"), m_configWidget);
407 buttons5Layout->addWidget(btnSaveWeights);
408 btnInitSnapshotting->setToolTip(tr("Save the weights to a file"));
409 connect(btnSaveWeights, SIGNAL(clicked(bool)), this, SLOT(saveWeights()));
411 // Load weights
412 QPushButton* btnLoadWeights = new QPushButton(tr("Load weights"), m_configWidget);
413 buttons5Layout->addWidget(btnLoadWeights);
414 btnLoadWeights->setToolTip(tr("Load the weights from a file"));
415 connect(btnLoadWeights, SIGNAL(clicked(bool)), this, SLOT(loadWeights()));
417 return m_configWidget;
421 * Slots
424 /// Reset DEM.
425 void tcElevationOptimization::resetDem()
429 /// Load elevation data from DEM.
430 void tcElevationOptimization::loadFromDem()
432 delete m_elevationData;
433 m_elevationData = 0;
435 delete m_datum;
436 m_datum = 0;
438 for (int i = 0; i < m_numDatasets; ++i)
440 delete m_data[i];
441 m_data[i] = 0;
444 m_loadingFromDem = true;
445 invalidate();
446 m_configWidget->requestRedraw();
447 m_loadingFromDem = false;
450 /// Apply hard constraints to elevation data.
451 void tcElevationOptimization::applyHardConstraints()
453 if (0 != m_elevationData)
455 const int width = m_elevationData->width();
456 const int height = m_elevationData->height();
457 // Now start optimising
458 startProcessing("Applying hard constraints");
459 for (int j = 0; j <= height; ++j)
461 progress((float)j/(height-1));
462 for (int i = 0; i <= width; ++i)
464 int index = j*width + i;
466 // Only apply constraints to unreliable pixels
467 PerPixelCache* cache = &m_datum->buffer()[index];
468 if (cache->reliability != 0)
470 continue;
473 int elevationConstraintCount = 0;
475 // Per dataset
476 for (int ds = 0; ds < m_numDatasets; ++ds)
478 PerDatasetPixelCache* dsCache = &m_data[ds]->buffer()[index];
480 bool isShadowed = (m_shadowData[ds]->sampleFloat((float)i/(width-1), (float)j/(height-1)) < 0.5f);
481 bool isShadowEntrance = isShadowed &&
482 dsCache->shadowTransition[TransitionEntrance][0] == i &&
483 dsCache->shadowTransition[TransitionEntrance][1] == j;
484 bool isShadowExit = isShadowed &&
485 dsCache->shadowTransition[TransitionExit][0] == i &&
486 dsCache->shadowTransition[TransitionExit][1] == j;
488 if (isShadowed)
490 bool exitReliable = false;
491 float exitElevation = 0.0f;
492 float exitDistance = 0.0f;
493 if (dsCache->shadowTransition[TransitionExit][0] >= 0 &&
494 dsCache->shadowTransition[TransitionExit][0] < width &&
495 dsCache->shadowTransition[TransitionExit][1] >= 0 &&
496 dsCache->shadowTransition[TransitionExit][1] < height)
498 int exitIndex = width*dsCache->shadowTransition[TransitionExit][1] + dsCache->shadowTransition[TransitionExit][0];
499 PerPixelCache* exitCache = &m_datum->buffer()[exitIndex];
500 // Great, we can say for definite the elevation of this pixel because the corresponding pixel is reliable
501 if (exitCache->reliability != 0)
503 exitReliable = true;
504 exitElevation = m_elevationData->buffer()[exitIndex];
505 exitDistance = dsCache->shadowTransitionDistance[TransitionExit];
506 if (isShadowEntrance)
508 float newElevation = exitElevation + exitDistance*dsCache->dlz_dlxy;
509 m_elevationData->buffer()[index] = m_elevationData->buffer()[index]*elevationConstraintCount + newElevation;
510 m_elevationData->buffer()[index] /= ++elevationConstraintCount;
511 cache->originalElevation = m_elevationData->buffer()[index];
512 cache->reliability = 1;
516 bool entranceReliable = false;
517 float entranceElevation = 0.0f;
518 float entranceDistance = 0.0f;
519 if (dsCache->shadowTransition[TransitionEntrance][0] >= 0 &&
520 dsCache->shadowTransition[TransitionEntrance][0] < width &&
521 dsCache->shadowTransition[TransitionEntrance][1] >= 0 &&
522 dsCache->shadowTransition[TransitionEntrance][1] < height)
524 int entranceIndex = width*dsCache->shadowTransition[TransitionEntrance][1] + dsCache->shadowTransition[TransitionEntrance][0];
525 PerPixelCache* entranceCache = &m_datum->buffer()[entranceIndex];
526 // Great, we can say for definite the elevation of this pixel because the corresponding pixel is reliable
527 if (entranceCache->reliability != 0)
529 entranceReliable = true;
530 entranceElevation = m_elevationData->buffer()[entranceIndex];
531 entranceDistance = dsCache->shadowTransitionDistance[TransitionEntrance];
532 if (isShadowExit)
534 float newElevation = entranceElevation - entranceDistance*dsCache->dlz_dlxy;
535 m_elevationData->buffer()[index] = m_elevationData->buffer()[index]*elevationConstraintCount + newElevation;
536 m_elevationData->buffer()[index] /= ++elevationConstraintCount;
537 cache->originalElevation = m_elevationData->buffer()[index];
538 cache->reliability = 1;
543 if (exitReliable && entranceReliable)
545 // maximum elevation is between elevation at entrance and elevation at exit
546 float maxElevation = (exitElevation*entranceDistance + entranceElevation*exitDistance) / (entranceDistance + exitDistance);
547 if (m_elevationData->buffer()[index] > maxElevation)
549 m_elevationData->buffer()[index] = maxElevation;
556 endProcessing();
558 invalidate();
559 m_configWidget->requestRedraw();
561 else
563 QMessageBox::warning(m_configWidget,
564 tr("Elevation field not initialised."),
565 tr("Please ensure the optimisation channel is displayed."));
569 /// Void-fill bilinear.
570 void tcElevationOptimization::voidFillBilinear()
572 if (0 != m_elevationData)
574 const int width = m_elevationData->width();
575 const int height = m_elevationData->height();
576 // Now start optimising
577 startProcessing("Bilinear void filling");
578 for (int j = 0; j < height; ++j)
580 progress(0.5f*j/(height-1));
581 int lastNonVoid = -1;
582 float lastNonVoidValue;
583 for (int i = 0; i < width; ++i)
585 int index = j*width + i;
586 if (m_datum->buffer()[index].reliability != 0)
588 float value = m_elevationData->buffer()[index];
589 if (lastNonVoid != -1)
591 int gap = i - lastNonVoid;
592 float startAlt = lastNonVoidValue;
593 float dAlt = (value - lastNonVoidValue) / gap;
594 // Lovely, between lastNonVoid and i is a void
595 for (int ii = lastNonVoid+1; ii < i; ++ii)
597 int iIndex = j*width + ii;
598 float alt = startAlt + dAlt * (ii - lastNonVoid);
599 m_elevationData->buffer()[iIndex] = alt;
600 // Keep track of the gap
601 m_datum->buffer()[iIndex].temporary.i = gap;
604 else
606 m_datum->buffer()[index].temporary.i = -1;
608 lastNonVoid = i;
609 lastNonVoidValue = value;
611 else
613 m_datum->buffer()[index].temporary.i = -1;
617 for (int i = 0; i < width; ++i)
619 progress(0.5f + 0.5f*i/(width-1));
620 int lastNonVoid = -1;
621 float lastNonVoidValue;
622 for (int j = 0; j < height; ++j)
624 int index = j*width + i;
625 float value = m_elevationData->buffer()[index];
626 if (m_datum->buffer()[index].reliability != 0)
628 if (lastNonVoid != -1)
630 int gap = j - lastNonVoid;
631 float startAlt = lastNonVoidValue;
632 float dAlt = (value - lastNonVoidValue) / gap;
633 // Lovely, between lastNonVoid and j is a void
634 for (int jj = lastNonVoid+1; jj < j; ++jj)
636 // Average with previous value if non zero, otherwise overwrite
637 int iIndex = jj*width + i;
638 float alt = startAlt + dAlt * (jj - lastNonVoid);
639 int otherGap = m_datum->buffer()[iIndex].temporary.i;
640 if (otherGap != -1)
642 float prevAlt = m_elevationData->buffer()[iIndex];
643 // Prefer the value of the smaller gap
644 alt = (alt * otherGap + prevAlt * gap) / (gap+otherGap);
646 m_elevationData->buffer()[iIndex] = alt;
649 lastNonVoid = j;
650 lastNonVoidValue = value;
654 endProcessing();
656 invalidate();
657 m_configWidget->requestRedraw();
659 else
661 QMessageBox::warning(m_configWidget,
662 tr("Elevation field not initialised."),
663 tr("Please ensure the optimisation channel is displayed."));
667 /// Void-fill bicubic.
668 void tcElevationOptimization::voidFillBicubic()
670 if (0 != m_elevationData)
672 const int width = m_elevationData->width();
673 const int height = m_elevationData->height();
674 // Now start optimising
675 startProcessing("Bicubic void filling");
676 for (int j = 0; j < height; ++j)
678 progress(0.5f*j/(height-1));
679 int lastNonVoid = -1;
680 float lastNonVoidValue;
681 for (int i = 0; i < width; ++i)
683 int index = j*width + i;
684 if (m_datum->buffer()[index].reliability != 0)
686 float value = m_elevationData->buffer()[index];
687 if (lastNonVoid != -1)
689 int gap = i - lastNonVoid;
690 // Obtain some control points for the splines
691 // use the two samples either side of the gap as control points 1 and 2
692 // use the ones before and after these samples, extended to the size of
693 // the gap as control points 0 and 3
694 float startAlt = lastNonVoidValue;
695 float beforeStartAlt = startAlt;
696 if (lastNonVoid > 0)
698 if (0 != m_datum->buffer()[j*width + lastNonVoid - 1].reliability)
700 beforeStartAlt = startAlt + (m_elevationData->buffer()[j*width + lastNonVoid - 1] - startAlt) * gap;
703 float endAlt = value;
704 float afterEndAlt = endAlt;
705 if (i < width-1)
707 if (0 != m_datum->buffer()[j*width + i + 1].reliability)
709 afterEndAlt = endAlt + (m_elevationData->buffer()[j*width + i + 1] - endAlt) * gap;
712 // Lovely, between lastNonVoid and i is a void
713 for (int ii = lastNonVoid+1; ii < i; ++ii)
715 int iIndex = j*width + ii;
716 float u = (float)(ii-lastNonVoid) / gap;
717 float u2 = u*u;
718 float u3 = u2*u;
719 float alt = maths::catmullRomSpline.Spline(u, u2, u3, beforeStartAlt, startAlt, endAlt, afterEndAlt);
720 m_elevationData->buffer()[iIndex] = alt;
721 // Keep track of the gap
722 m_datum->buffer()[iIndex].temporary.i = gap;
725 else
727 m_datum->buffer()[index].temporary.i = -1;
729 lastNonVoid = i;
730 lastNonVoidValue = value;
732 else
734 m_datum->buffer()[index].temporary.i = -1;
738 for (int i = 0; i < width; ++i)
740 progress(0.5f + 0.5f*i/(width-1));
741 int lastNonVoid = -1;
742 float lastNonVoidValue;
743 for (int j = 0; j < height; ++j)
745 int index = j*width + i;
746 float value = m_elevationData->buffer()[index];
747 if (m_datum->buffer()[index].reliability != 0)
749 if (lastNonVoid != -1)
751 int gap = j - lastNonVoid;
752 // Obtain some control points for the splines
753 // use the two samples either side of the gap as control points 1 and 2
754 // use the ones before and after these samples, extended to the size of
755 // the gap as control points 0 and 3
756 float startAlt = lastNonVoidValue;
757 float beforeStartAlt = startAlt;
758 if (lastNonVoid > 0)
760 if (0 != m_datum->buffer()[(lastNonVoid-1)*width + i].reliability)
762 beforeStartAlt = startAlt + (m_elevationData->buffer()[(lastNonVoid-1)*width + i] - startAlt) * gap;
765 float endAlt = value;
766 float afterEndAlt = endAlt;
767 if (j < height-1)
769 if (0 != m_datum->buffer()[(j+1)*width + i].reliability)
771 afterEndAlt = endAlt + (m_elevationData->buffer()[(j+1)*width + i] - endAlt) * gap;
774 // Lovely, between lastNonVoid and j is a void
775 for (int jj = lastNonVoid+1; jj < j; ++jj)
777 // Average with previous value if non zero, otherwise overwrite
778 int iIndex = jj*width + i;
779 float u = (float)(jj-lastNonVoid) / gap;
780 float u2 = u*u;
781 float u3 = u2*u;
782 float alt = maths::catmullRomSpline.Spline(u, u2, u3, beforeStartAlt, startAlt, endAlt, afterEndAlt);
783 int otherGap = m_datum->buffer()[iIndex].temporary.i;
784 if (otherGap != -1)
786 float prevAlt = m_elevationData->buffer()[iIndex];
787 // Prefer the value of the smaller gap
788 alt = (alt * otherGap + prevAlt * gap) / (gap+otherGap);
790 m_elevationData->buffer()[iIndex] = alt;
793 lastNonVoid = j;
794 lastNonVoidValue = value;
798 endProcessing();
800 invalidate();
801 m_configWidget->requestRedraw();
803 else
805 QMessageBox::warning(m_configWidget,
806 tr("Elevation field not initialised."),
807 tr("Please ensure the optimisation channel is displayed."));
811 /// Start/stop optimization process.
812 void tcElevationOptimization::startStopOptimize()
814 if (m_processingMutex.tryLock())
816 m_btnOptimize->setText(tr("Stop Optimization"));
817 m_btnOptimize->update();
818 if (!m_thread)
820 m_thread = new WorkerThread(this);
822 m_thread->start();
824 else
826 // Warn the worker thread that its time to stop.
827 m_stopProcessing = true;
831 /// Optimize a number of times.
832 void tcElevationOptimization::optimize()
834 if (0 != m_elevationData)
836 const int width = m_elevationData->width();
837 const int height = m_elevationData->height();
838 // Now start optimising
839 startProcessing("Optimizing elevation data");
840 int passes = m_spinIterations->value();
841 for (int i = 0; i < passes; ++i)
843 startProcessing("Optimizing elevation data");
844 float passage = (float)i/(passes-1);
845 float passageNeg = 1.0f - passageNeg;
846 progress(passage);
847 for (int w = 0; w < WeightsMax; ++w)
849 float start = m_weightLimits[0][w] + m_weightLimits[1][w]*m_weightSliders[0][w]->value();
850 float end = m_weightLimits[0][w] + m_weightLimits[1][w]*m_weightSliders[1][w]->value();
851 m_weights[w] = passageNeg*start + passage*end;
853 float deltaH = m_weights[ElevationStep];
854 float range = m_weights[Range];
855 optimizeElevations(0, 0, width-1, height-1, deltaH, range);
856 if (m_stopProcessing)
858 emit invalidateAndRedraw();
859 endProcessing();
860 break;
863 if ((i+1) % 5 == 0 || i == passes-1)
865 emit invalidateAndRedraw();
866 startProcessing("Optimizing elevation data");
867 progress((float)i/passes);
870 sampleStdDeviations();
872 endProcessing();
874 else
876 QMessageBox::warning(m_configWidget,
877 tr("Elevation field not initialised."),
878 tr("Please ensure the optimisation channel is displayed."));
882 /// Clear standard deviations.
883 void tcElevationOptimization::clearStdDeviations()
885 m_plotX.clear();
886 m_stdDevData.clear();
887 m_stdDevCurve->setData(m_plotX, m_stdDevData);
888 m_stdDevVoidData.clear();
889 m_stdDevVoidCurve->setData(m_plotX, m_stdDevVoidData);
890 m_stdDevNonVoidData.clear();
891 m_stdDevNonVoidCurve->setData(m_plotX, m_stdDevNonVoidData);
892 m_plot->replot();
893 m_plot->update();
896 /// Sample standard deviations.
897 void tcElevationOptimization::sampleStdDeviations()
899 int width = m_elevationData->width();
900 int height = m_elevationData->height();
901 float stdDevVoid = 0.0f;
902 float stdDevNonVoid = 0.0f;
903 float stdDevAll = 0.0f;
904 int total = 0;
905 int voids = 0;
906 int marginx = height>>3;
907 int marginy = width>>3;
908 for (int j = marginy; j < height-marginy; ++j)
910 for (int i = marginx; i < width-marginx; ++i)
912 int index = j*width + i;
913 float altitude = m_datum->buffer()[index].accurateElevation;
915 float square = m_elevationData->buffer()[index] - altitude;
916 square *= square;
917 stdDevAll += square;
918 if (m_datum->buffer()[index].reliability)
920 stdDevNonVoid += square;
922 else
924 stdDevVoid += square;
925 ++voids;
927 ++total;
930 stdDevNonVoid = sqrt(stdDevNonVoid / (total-voids));
931 stdDevVoid = sqrt(stdDevVoid / voids);
932 stdDevAll = sqrt(stdDevAll / total);
933 emit statistics(tr("Overall std deviation: %1 m\n"
934 "Void std deviation: %2 (%4\%) m\n"
935 "Non-void std deviation: %3 (%5\%) m")
936 .arg(stdDevAll)
937 .arg(stdDevVoid).arg(stdDevNonVoid)
938 .arg(100.0f*voids/total).arg(100.0f*(total-voids)/total));
939 //m_configWidget->update();
941 if (m_snapshotReadFile.isEmpty() || m_snapshotReading)
943 m_plotX.append(m_plotX.size()+1);
944 m_stdDevData.append(stdDevAll);
945 m_stdDevCurve->setData(m_plotX, m_stdDevData);
946 m_stdDevVoidData.append(stdDevVoid);
947 m_stdDevVoidCurve->setData(m_plotX, m_stdDevVoidData);
948 m_stdDevNonVoidData.append(stdDevNonVoid);
949 m_stdDevNonVoidCurve->setData(m_plotX, m_stdDevNonVoidData);
950 // this will get queued for event loop
951 emit replotStdDeviations();
953 // Take a snapshot
954 if (!m_snapshotFile.isEmpty())
956 QString fname = QString("%1.%2").arg(m_snapshotFile).arg(m_plotX.size(), 4, 10, QLatin1Char('0'));
957 QFile snap(fname);
958 if (snap.open(QIODevice::WriteOnly))
960 QDataStream stream(&snap);
961 stream.setByteOrder(QDataStream::LittleEndian);
962 stream << width << height;
963 int index = 0;
964 for (int j = 0; j < height; ++j)
966 for (int i = 0; i < width; ++i)
968 uint16_t alt = floor(0.5f + m_elevationData->buffer()[index]);
969 uint16_t orig = floor(0.5f + m_datum->buffer()[index].originalElevation);
970 if (!m_datum->buffer()[index].reliability)
972 alt |= 0x8000;
974 stream << alt << orig;
975 ++index;
983 /// Sample standard deviations.
984 void tcElevationOptimization::saveStdDeviations()
986 QString filename = QFileDialog::getSaveFileName(m_configWidget,
987 tr("Save standard deviation data"),
988 QString(),
989 tr("ASCII Data Files (*.adf)"));
991 if (!filename.isEmpty())
993 // Open the file ready to write the data
994 QFile file(filename);
995 if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
997 QMessageBox::warning(m_configWidget,
998 tr("Open failed."),
999 tr("Could not open '%1' for writing.").arg(filename));
1000 return;
1003 QTextStream out(&file);
1005 for (int i = 0; i < m_plotX.size(); ++i)
1007 out << m_plotX[i] << "\t"
1008 << m_stdDevData[i] << "\t"
1009 << m_stdDevVoidData[i] << "\t"
1010 << m_stdDevNonVoidData[i] << "\n";
1015 /// Save cross section data.
1016 void tcElevationOptimization::saveCrossSections()
1018 if (!m_crossSectioned)
1020 QMessageBox::warning(m_configWidget,
1021 tr("Cross section save failed."),
1022 tr("You need to select a cross section with middle mouse button drag."));
1023 return;
1026 QString filename = QFileDialog::getSaveFileName(m_configWidget,
1027 tr("Save cross section data"),
1028 QString(),
1029 tr("Crosssection Files (*.xsec)"));
1031 if (!filename.isEmpty())
1033 // Open the file ready to write the data
1034 QFile file(filename);
1035 if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
1037 QMessageBox::warning(m_configWidget,
1038 tr("Open failed."),
1039 tr("Could not open '%1' for writing.").arg(filename));
1040 return;
1043 QTextStream out(&file);
1045 int resolution = m_crossResolution->value();
1046 int frequency = m_crossFrequency->value();
1047 int snaps = m_snapshotSlider->maximum();
1048 tcGeo delta = (m_crossSection[1] - m_crossSection[0]) / (resolution-1);
1049 // save distances
1051 float deltaDist = tcGeo::angleBetween(m_crossSection[1], m_crossSection[0])/(resolution-1)*6378.137e3;
1052 float distance = 0.0f;
1053 for (int p = 0; p < resolution; ++p)
1055 out << distance << ((p == resolution-1) ? "\n" : "\t");
1056 distance += deltaDist;
1059 for (int snap = 1; snap <= snaps; snap += frequency)
1061 // Load snapshot
1062 loadSnapShot(snap);
1064 tcGeo tmp = m_crossSection[0];
1065 for (int p = 0; p < resolution; ++p)
1067 float elev = m_elevationData->elevationAt(tmp);
1068 out << elev << ((p == resolution-1) ? "\n" : "\t");
1069 tmp.setLon(tmp.lon() + delta.lon());
1070 tmp.setLat(tmp.lat() + delta.lat());
1076 /// Invalidate and redraw.
1077 void tcElevationOptimization::invalidateAndRedrawSlot()
1079 invalidate();
1080 m_configWidget->requestRedraw();
1083 /// Initialise snapshotting.
1084 void tcElevationOptimization::initSnapShotting()
1086 m_snapshotReadFile = QString();
1087 m_snapshotSlider->setEnabled(false);
1088 m_snapshotFile = QFileDialog::getSaveFileName(m_configWidget,
1089 tr("Save snapshotting config file"),
1090 QString(),
1091 tr("Snapshot Config Files (*.sdf)"));
1093 if (!m_snapshotFile.isEmpty())
1095 // Open the file ready to write the data
1096 QFile file(m_snapshotFile);
1097 if (!file.open(QIODevice::WriteOnly))
1099 QMessageBox::warning(m_configWidget,
1100 tr("Open failed."),
1101 tr("Could not open '%1' for writing.").arg(m_snapshotFile));
1102 return;
1105 const double* portion = portionPosition();
1106 tcGeo p1(imagery()->texToGeo()*maths::Vector<2,double>(portion[0], portion[1]));
1107 tcGeo p2(imagery()->texToGeo()*maths::Vector<2,double>(portion[2], portion[3]));
1109 QDataStream stream(&file);
1110 stream.setByteOrder(QDataStream::LittleEndian);
1111 stream << p1.lon()*180.0/M_PI
1112 << p1.lat()*180.0/M_PI
1113 << p2.lon()*180.0/M_PI
1114 << p2.lat()*180.0/M_PI;
1118 #include <QtDebug>
1119 /// Load a sequence of snapshots.
1120 void tcElevationOptimization::loadSnapShots()
1122 m_snapshotFile = QString();
1123 m_snapshotReadFile = QFileDialog::getOpenFileName(m_configWidget,
1124 tr("Open snapshotting config file"),
1125 QString(),
1126 tr("Snapshot Config Files (*.sdf)"));
1128 if (!m_snapshotReadFile.isEmpty())
1130 // Open the file ready to read the data
1131 QFile file(m_snapshotReadFile);
1132 if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
1134 QMessageBox::warning(m_configWidget,
1135 tr("Open failed."),
1136 tr("Could not open '%1' for reading.").arg(m_snapshotReadFile));
1137 return;
1140 QDataStream stream(&file);
1141 stream.setByteOrder(QDataStream::LittleEndian);
1143 double portion[2][2];
1144 stream >> portion[0][0]
1145 >> portion[0][1]
1146 >> portion[1][0]
1147 >> portion[1][1];
1148 tcGeo p1(portion[0][0]*M_PI/180.0, portion[0][1]*M_PI/180.0);
1149 tcGeo p2(portion[1][0]*M_PI/180.0, portion[1][1]*M_PI/180.0);
1151 // force update of region
1152 m_configWidget->requestSlice(p1, p2);
1154 // Find number of snaps
1155 int max = 0;
1156 bool exists = true;
1157 QFile snap;
1158 while (exists)
1160 ++max;
1161 QString fname = QString("%1.%2").arg(m_snapshotReadFile).arg(max, 4, 10, QLatin1Char('0'));
1162 snap.setFileName(fname);
1163 exists = snap.exists();
1165 --max;
1166 m_snapshotSlider->setRange(1, max);
1167 m_snapshotSlider->setEnabled(true);
1169 // load first sample
1170 clearStdDeviations();
1171 m_snapshotReading = true;
1172 for (int i = 1; i <= max; ++i)
1174 loadSnapShot(i);
1176 m_snapshotReading = false;
1177 loadSnapShot(max);
1178 m_snapshotSlider->setValue(max);
1180 else
1182 m_snapshotSlider->setEnabled(false);
1186 /// Update weight labels.
1187 void tcElevationOptimization::updateWeightLabels()
1189 for (int i = 0; i < WeightsMax; ++i)
1191 m_weightLabels[i]->setText(QString("%1 -> %2")
1192 .arg(m_weightLimits[0][i] + m_weightLimits[1][i]*m_weightSliders[0][i]->value())
1193 .arg(m_weightLimits[0][i] + m_weightLimits[1][i]*m_weightSliders[1][i]->value())
1198 /// Save weights.
1199 void tcElevationOptimization::saveWeights()
1201 QString filename = QFileDialog::getSaveFileName(m_configWidget,
1202 tr("Save weights"),
1203 QString(),
1204 tr("Elevation optimization weights (*.eow)"));
1206 if (!filename.isEmpty())
1208 // Open the file ready to write the data
1209 QFile file(filename);
1210 if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
1212 QMessageBox::warning(m_configWidget,
1213 tr("Open failed."),
1214 tr("Could not open '%1' for writing.").arg(filename));
1215 return;
1218 QTextStream stream(&file);
1219 for (int w = 0; w < WeightsMax; ++w)
1221 float start = m_weightLimits[0][w] + m_weightLimits[1][w]*m_weightSliders[0][w]->value();
1222 float end = m_weightLimits[0][w] + m_weightLimits[1][w]*m_weightSliders[1][w]->value();
1223 stream << start << "\t" << end << "\n";
1228 /// Load weights.
1229 void tcElevationOptimization::loadWeights()
1231 QString filename = QFileDialog::getOpenFileName(m_configWidget,
1232 tr("Load weights"),
1233 QString(),
1234 tr("Elevation optimization weights (*.eow)"));
1236 if (!filename.isEmpty())
1238 // Open the file ready to write the data
1239 QFile file(filename);
1240 if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
1242 QMessageBox::warning(m_configWidget,
1243 tr("Load failed."),
1244 tr("Could not open '%1' for reading.").arg(filename));
1245 return;
1248 QTextStream stream(&file);
1249 float starts[WeightsMax], ends[WeightsMax];
1250 for (int w = 0; w < WeightsMax; ++w)
1252 stream >> starts[w] >> ends[w];
1253 if (stream.atEnd())
1255 if (w == WeightsMax-1)
1257 // Go back to shading shifting the values
1258 while (w > PhotometricStereo)
1260 starts[w] = starts[w-1];
1261 ends[w] = ends[w-1];
1262 --w;
1264 starts[w] = ends[w] = 0.0f;
1265 break;
1267 else
1269 starts[w] = ends[w] = 0.0f;
1270 QMessageBox::warning(m_configWidget,
1271 tr("Panic!"),
1272 tr("Not enough weights"));
1276 for (int w = 0; w < WeightsMax; ++w)
1278 m_weightSliders[0][w]->setValue((starts[w] - m_weightLimits[0][w]) / m_weightLimits[1][w]);
1279 m_weightSliders[1][w]->setValue((ends[w] - m_weightLimits[0][w]) / m_weightLimits[1][w]);
1284 /// Load a single snapshot.
1285 void tcElevationOptimization::loadSnapShot(int id)
1287 QString counter = "-";
1288 // Load a snapshot
1289 if (!m_snapshotReadFile.isEmpty())
1291 QString fname = QString("%1.%2").arg(m_snapshotReadFile).arg(id, 4, 10, QLatin1Char('0'));
1292 QFile snap(fname);
1293 if (snap.open(QIODevice::ReadOnly))
1295 QDataStream stream(&snap);
1296 stream.setByteOrder(QDataStream::LittleEndian);
1297 int width, height;
1298 stream >> width >> height;
1299 if (m_elevationData->width() != width || m_elevationData->height() != height)
1301 QMessageBox::warning(m_configWidget,
1302 tr("Snapshot dimentions do not match with current."),
1303 tr("Panic!"));
1305 else
1307 int index = 0;
1308 for (int j = 0; j < height; ++j)
1310 for (int i = 0; i < width; ++i)
1312 uint16_t alt;
1313 uint16_t orig;
1314 stream >> alt >> orig;
1315 m_datum->buffer()[index].reliability = !(alt & 0x8000);
1316 m_datum->buffer()[index].originalElevation = orig;
1317 alt &= 0x7FFF;
1318 m_elevationData->buffer()[index] = alt;
1319 ++index;
1322 sampleStdDeviations();
1323 if (!m_snapshotReading)
1325 invalidate();
1326 m_configWidget->requestRedraw();
1328 counter = QString("%1").arg(id);
1332 m_snapshotCounter->setText(counter);
1336 * Interface for derived class to implement
1339 void tcElevationOptimization::roundPortion(double* x1, double* y1, double* x2, double* y2)
1341 //m_shadowChannel->roundPortion(x1,y1,x2,y2);
1344 tcAbstractPixelData* tcElevationOptimization::loadPortion(double x1, double y1, double x2, double y2, bool changed)
1346 int width = -1;
1347 int height = -1;
1349 for (int i = 0; i < m_numDatasets; ++i)
1351 maths::Vector<2,double> xy1 = m_texToDatasetTex[i] * maths::Vector<2,double>(x1,y1);
1352 maths::Vector<2,double> xy2 = m_texToDatasetTex[i] * maths::Vector<2,double>(x2,y2);
1353 Reference<tcAbstractPixelData> channelData = m_shadowChannels[i]->portion(xy1[0], xy1[1], xy2[0], xy2[1]);
1354 if (i == 0)
1356 width = channelData->width();
1357 height = channelData->height();
1359 m_shadowData[i] = dynamicCast<tcPixelData<float>*>(channelData);
1360 if (0 == m_shadowData[i])
1362 return 0;
1364 if (0 != m_shadingChannels[i])
1366 Reference<tcAbstractPixelData> shadingData = m_shadingChannels[i]->portion(xy1[0], xy1[1], xy2[0], xy2[1]);
1367 m_shadingData[i] = dynamicCast<tcPixelData<float>*>(shadingData);
1369 else
1371 m_shadingData[i] = 0;
1375 /// @todo What if its a different size because the portion has changed?
1376 if (0 == m_elevationData || 0 == m_datum || changed)
1378 delete m_elevationData;
1379 for (int i = 0; i < m_numDatasets; ++i)
1381 delete m_data[i];
1383 delete m_datum;
1385 // Create a new pixel buffer, initialised to altitude
1387 m_elevationData = new tcElevationData(width, height);
1388 // Find transform of elevation data
1389 tcGeo geoBase = geoAt(maths::Vector<2,float>(x1, y1));
1390 tcGeo geoDiagonal = geoAt(maths::Vector<2,float>(x1 + (x2-x1)/(width-1), y1 + (y2-y1)/(height-1))) - geoBase;
1391 m_elevationData->setGeoToPixels(tcAffineTransform2<double>(geoBase, geoDiagonal).inverse());
1393 m_datum = new tcTypedPixelData<PerPixelCache>(width, height);
1394 for (int i = 0; i < m_numDatasets; ++i)
1396 m_data[i] = new tcTypedPixelData<PerDatasetPixelCache>(width, height);
1399 int maxWidthHeight = qMax(width, height);
1400 double minXY12 = qMin(x2-x1, y2-y1);
1402 startProcessing("Caching elevation characteristics");
1403 for (int j = 0; j < height; ++j)
1405 progress((float)j/(height-1));
1406 for (int i = 0; i < width; ++i)
1408 int index = j*width + i;
1409 // Transform coordinates
1410 maths::Vector<2,float> coord (x1 + (x2-x1)*i / (width - 1),
1411 y1 + (y2-y1)*j / (height - 1));
1412 tcGeo geoCoord = geoAt(coord);
1414 PerPixelCache* cache = &m_datum->buffer()[index];
1416 // Find resolution
1417 maths::Vector<2,float> coordx (x1 + (x2-x1)*(i+1) / (width - 1),
1418 y1 + (y2-y1)*j / (height - 1));
1419 maths::Vector<2,float> coordy (x1 + (x2-x1)*i / (width - 1),
1420 y1 + (y2-y1)*(j+1) / (height - 1));
1421 tcGeo geoCoordx = geoAt(coordx) - geoCoord;
1422 tcGeo geoCoordy = geoAt(coordy) - geoCoord;
1423 // Find approx
1424 float res = geoCoordx.angle();
1425 cache->resolution[0] = res*cos(geoCoord.lat());
1426 cache->resolution[1] = geoCoordy.angle();
1427 cache->resolution *= 6378.137e3;
1429 // Get some elevation data
1430 bool accurate;
1431 float altitude = altitudeAt(geoCoord, true, &accurate);
1432 m_elevationData->buffer()[index] = altitude;
1433 cache->originalElevation = altitude;
1434 cache->reliability = accurate ? 1 : 0;
1435 cache->accurateElevation = dem()[1].altitudeAt(geoCoord, true, 0);
1437 // Per dataset cache
1438 for (int ds = 0; ds < m_numDatasets; ++ds)
1440 tcGeoImageData* imagery = m_datasets[ds];
1441 maths::Vector<3,float> light = lightDirectionAt(geoCoord, imagery);
1443 PerDatasetPixelCache* dsCache = &m_data[ds]->buffer()[index];
1444 dsCache->light = light;
1445 dsCache->lxy = light.slice<0,2>().mag();
1446 dsCache->dlz_dlxy = light[2]/dsCache->lxy;
1448 tcGeo lightScaled(light[0]*res/sin(geoCoord.lat()),
1449 light[1]*res);
1450 tcGeo offset(geoCoord + lightScaled);
1451 for (int depthDirection = 0; depthDirection < 2; ++depthDirection)
1453 tcGeo pos = geoCoord;
1454 tcGeo delta;
1455 if (depthDirection == TransitionEntrance)
1457 delta = offset - geoCoord;
1459 else
1461 delta = geoCoord - offset;
1463 delta = delta / 2;
1464 pos = pos + delta;
1465 maths::Vector<2,float> tex = coord;
1466 int transitionI = i;
1467 int transitionJ = j;
1468 while (transitionI >= 0 && transitionI < width && transitionJ >= 0 && transitionJ < height)
1470 if (m_shadowData[ds]->sampleFloat((tex[0]-x1)/(x2-x1), (tex[1]-y1)/(y2-y1)) > 0.5f)
1472 break;
1474 transitionI = 0.5f + (tex[0]-x1)/(x2-x1)*(width -1);
1475 transitionJ = 0.5f + (tex[1]-y1)/(y2-y1)*(height-1);
1476 pos = pos + delta;
1477 tex = textureAt(pos);
1479 dsCache->shadowTransition[depthDirection][0] = transitionI;
1480 dsCache->shadowTransition[depthDirection][1] = transitionJ;
1481 dsCache->shadowTransitionDistance[depthDirection] = tcGeo::angleBetween(pos,geoCoord)*6378.137e3;
1487 * matrix L={Light row vec of shading channel i;..}
1488 * invert L
1489 * multiply by shading values for shading channels
1490 * should be mostly about the same magnitude
1491 * normalize
1493 // Estimate a normal from the shading channels
1494 maths::Vector<3,float> totalN(0.0f, 0.0f, 0.01f);
1496 int ds[3];
1497 for (ds[0] = 0; ds[0] < m_numDatasets; ++ds[0])
1499 if (0 == m_shadingData[ds[0]])
1501 continue;
1503 for (ds[1] = ds[0]+1; ds[1] < m_numDatasets; ++ds[1])
1505 if (0 == m_shadingData[ds[1]])
1507 continue;
1509 for (ds[2] = ds[1]+1; ds[2] < m_numDatasets; ++ds[2])
1511 if (0 == m_shadingData[ds[2]])
1513 continue;
1515 maths::Matrix<3,float> L(m_data[ds[0]]->buffer()[index].light,
1516 m_data[ds[1]]->buffer()[index].light,
1517 m_data[ds[2]]->buffer()[index].light);
1518 maths::Vector<3,float> S;
1519 bool singular = false;
1520 L.invert(&singular);
1521 if (singular)
1523 continue;
1525 for (int dsi = 0; dsi < 3; ++dsi)
1527 S[dsi] = m_shadingData[ds[dsi]]->sampleFloat((float)i/(width-1), (float)j/(height-1));
1529 maths::Vector<3,float> N = S * L;
1530 N.normalize();
1531 totalN += N;
1535 cache->normalEstimate = totalN;
1536 cache->normalEstimate.normalize();
1542 if (!m_loadingFromDem)
1544 // Update elevation model
1545 startProcessing("Updating elevation model");
1546 dem()->updateFromElevationData(m_elevationData);
1549 // Don't bother outputting anything
1550 //return new tcTypedPixelData<unsigned char>(1,1);
1552 // Downscale the elevation for viewing
1553 startProcessing("Downscaling for viewing");
1554 tcPixelData<GLubyte>* result = new tcPixelData<GLubyte>(width, height);
1555 //tcTypedPixelData<maths::Vector<3,float> >* result = new tcTypedPixelData<maths::Vector<3,float> >(width, height);
1556 Q_ASSERT(0 != result);
1558 int index = 0;
1559 for (int j = 0; j < height; ++j)
1561 progress((float)j/(height-1));
1562 for (int i = 0; i < width; ++i)
1565 PerPixelCache* cache = &m_datum->buffer()[index];
1567 result->buffer()[index] = 255 * cache->reliability;
1568 ++index;
1569 continue;
1571 #if 0
1572 if (0)
1574 float dh_dx = 0.0f;
1575 float dh_dy = 0.0f;
1576 if (i > 0 && i < width-1)
1578 float hp1 = m_elevationData->buffer()[index+1];
1579 float hm1 = m_elevationData->buffer()[index-1];
1580 dh_dx = (hp1-hm1)/2 / cache->resolution[0];
1582 if (j > 0 && j < height-1)
1584 float hp1 = m_elevationData->buffer()[index+width];
1585 float hm1 = m_elevationData->buffer()[index-width];
1586 dh_dy = (hp1-hm1)/2 / cache->resolution[1];
1588 // (0.0f, -1.0f, dh_dy) x (1.0f, 0.0f, dh_dx)
1589 maths::Vector<3,float> norm(-dh_dx, dh_dy, 1.0f);
1590 norm.normalize();
1591 result->buffer()[index] = norm;
1593 #endif
1595 #if 0
1596 for (int ds = 0; ds < m_numDatasets; ++ds)
1598 result->buffer()[index] = 0;
1599 PerDatasetPixelCache* dsCache = &m_data[ds]->buffer()[index];
1601 float hwm1 = m_elevationData->sampleFloat(
1602 ((float)i + dsCache->light[0]/cache->resolution[0])/(width-1),
1603 ((float)j - dsCache->light[1]/cache->resolution[1])/(height-1)
1605 float hwp1 = m_elevationData->sampleFloat(
1606 ((float)i - dsCache->light[0]/cache->resolution[0])/(width-1),
1607 ((float)j + dsCache->light[1]/cache->resolution[1])/(height-1)
1609 float dh_dw = (hwm1 - hwp1)/(2*dsCache->lxy);
1610 float facingness = dsCache->dlz_dlxy-dh_dw;
1611 // if (facingness < 0.0f)
1612 // result->buffer()[index] = 128.0f;
1614 result->buffer()[index] = 128.0f * facingness*facingness;
1616 result->buffer()[index] = 0;
1618 bool isShadowed = (m_shadowData[ds]->sampleFloat((float)i/(width-1), (float)j/(height-1)) < 0.5f);
1619 bool isShadowEntrance = isShadowed &&
1620 dsCache->shadowTransition[TransitionEntrance][0] == i &&
1621 dsCache->shadowTransition[TransitionEntrance][1] == j;
1622 bool isShadowExit = isShadowed &&
1623 dsCache->shadowTransition[TransitionExit][0] == i &&
1624 dsCache->shadowTransition[TransitionExit][1] == j;
1625 float elevation = m_elevationData->buffer()[index];
1626 result->buffer()[index] = (elevation - 2500.0f)/1500.0f*255.0f;
1628 if (false && isShadowed)
1630 // maximum elevation is between elevation at entrance and elevation at exit
1631 if (m_weights[BelowShadowLine] != 0.0f &&
1632 dsCache->shadowTransition[TransitionExit][0] >= 0 &&
1633 dsCache->shadowTransition[TransitionExit][0] < width &&
1634 dsCache->shadowTransition[TransitionExit][1] >= 0 &&
1635 dsCache->shadowTransition[TransitionExit][1] < height &&
1636 dsCache->shadowTransition[TransitionEntrance][0] >= 0 &&
1637 dsCache->shadowTransition[TransitionEntrance][0] < width &&
1638 dsCache->shadowTransition[TransitionEntrance][1] >= 0 &&
1639 dsCache->shadowTransition[TransitionEntrance][1] < height)
1641 int exitIndex = width*dsCache->shadowTransition[TransitionExit][1] + dsCache->shadowTransition[TransitionExit][0];
1642 int entranceIndex = width*dsCache->shadowTransition[TransitionEntrance][1] + dsCache->shadowTransition[TransitionEntrance][0];
1643 float exitElevation = m_elevationData->buffer()[exitIndex];
1644 float entranceElevation = m_elevationData->buffer()[entranceIndex];
1646 result->buffer()[index] = qMax(0.0f, elevation - (exitElevation * dsCache->shadowTransitionDistance[TransitionEntrance]
1647 +entranceElevation * dsCache->shadowTransitionDistance[TransitionExit])
1648 / (dsCache->shadowTransitionDistance[TransitionEntrance]
1649 +dsCache->shadowTransitionDistance[TransitionExit]));
1653 result->buffer()[index] = 128.0f*m_shadingData[ds]->sampleFloat((float)i/(width-1), (float)j/(height-1));
1655 #if 0
1656 int xdist = abs(dsCache->shadowTransition[TransitionEntrance][0] - i);
1657 int ydist = abs(dsCache->shadowTransition[TransitionEntrance][1] - j);
1658 bool bothIn = isShadowed && (dsCache->shadowTransition[TransitionExit][0] >= 0 &&
1659 dsCache->shadowTransition[TransitionExit][0] < width &&
1660 dsCache->shadowTransition[TransitionExit][1] >= 0 &&
1661 dsCache->shadowTransition[TransitionExit][1] < height &&
1662 dsCache->shadowTransition[TransitionEntrance][0] >= 0 &&
1663 dsCache->shadowTransition[TransitionEntrance][0] < width &&
1664 dsCache->shadowTransition[TransitionEntrance][1] >= 0 &&
1665 dsCache->shadowTransition[TransitionEntrance][1] < height);
1667 if (isShadowEntrance && (dsCache->shadowTransition[TransitionExit][0] >= 0 &&
1668 dsCache->shadowTransition[TransitionExit][0] < width &&
1669 dsCache->shadowTransition[TransitionExit][1] >= 0 &&
1670 dsCache->shadowTransition[TransitionExit][1] < height))
1672 int exitIndex = width*dsCache->shadowTransition[TransitionExit][1] + dsCache->shadowTransition[TransitionExit][0];
1674 float exitElevation = m_elevationData->buffer()[exitIndex];
1675 float diff = exitElevation + dsCache->shadowTransitionDistance[TransitionExit]*dsCache->dlz_dlxy - elevation;
1676 result->buffer()[index] += diff/5000.0f*255.0f;
1678 else
1680 result->buffer()[index] = 0;
1682 #endif
1684 // */
1686 //result->buffer()[index] = isShadowed ? qMin(255, 100 * (xdist + ydist)) : 255;
1687 /*result->buffer()[index] = (isShadowed && (dsCache->shadowTransition[TransitionExit][0] >= 0 &&
1688 dsCache->shadowTransition[TransitionExit][0] < width &&
1689 dsCache->shadowTransition[TransitionExit][1] >= 0 &&
1690 dsCache->shadowTransition[TransitionExit][1] < height))?255:0;*/
1691 //float mycostChange = 100000.0f*255.0f*costChange(i, j, +4.0f, 0);
1692 //result->buffer()[index] = 128.0f-mycostChange;
1693 //result->buffer()[index] = m_datum->buffer()[index].reliability*255;
1695 #else
1697 #if 0
1698 result->buffer()[index] = cache->normalEstimate;
1699 #endif
1701 #endif
1703 ++index;
1707 endProcessing();
1709 return result;
1712 template <typename T>
1713 T MySqr(T t)
1715 return t*t;
1719 * Private functions
1722 #include <QtDebug>
1723 /// Calculate the cost for a set of pixels.
1724 float tcElevationOptimization::cost(int x1, int y1, int x2, int y2) const
1726 int width = m_elevationData->width();
1727 int height = m_elevationData->height();
1728 if (x1 < 0)
1730 x1 = 0;
1732 if (x2 >= width)
1734 x2 = width-1;
1736 if (y1 < 0)
1738 y1 = 0;
1740 if (y2 >= height)
1742 y2 = height-1;
1745 int maxWidthHeight = qMax(width, height);
1746 //double minXY12 = qMin(m_window.x2-m_window.x1, m_window.y2-m_window.y1);
1748 float costVariance = 0.0f;
1749 int countVariance = 0;
1750 float costRoughness = 0.0f;
1751 int countRoughness = 0;
1752 float costSteepness = 0.0f;
1753 int countSteepness = 0;
1754 float costLitFacing = 0.0f;
1755 int countLitFacing = 0;
1756 float costShading = 0.0f;
1757 int countShading = 0;
1758 float costStereo = 0.0f;
1759 int countStereo = 0;
1760 float costBelowShadowLine = 0.0f;
1761 int countBelowShadowLine = 0;
1762 float costTransitionElevTop = 0.0f;
1763 int countTransitionElevTop = 0;
1764 float costTransitionElevBottom = 0.0f;
1765 int countTransitionElevBottom = 0;
1766 float costTransitionTangential = 0.0f;
1767 int countTransitionTangential = 0;
1768 float costTransitionCurvingDown = 0.0f;
1769 int countTransitionCurvingDown = 0;
1770 for (int j = y1; j <= y2; ++j)
1772 for (int i = x1; i <= x2; ++i)
1774 float* elevationPtr = pixelElevation(i, j);
1775 if (0 == elevationPtr)
1777 continue;
1780 int index = j*width + i;
1782 // Basic data retrieval
1783 float elevation = *elevationPtr;
1784 PerPixelCache* cache = &m_datum->buffer()[index];
1785 if (m_voidsOnly && cache->reliability == 0)
1787 continue;
1790 // Gradient
1791 // Derivitive of gradient
1792 float dh_dx = 0.0f;
1793 float dh_dy = 0.0f;
1794 float d2h_dx2 = 0.0f;
1795 float d2h_dy2 = 0.0f;
1796 if (i > 0 && i < width-1)
1798 float hp1 = m_elevationData->buffer()[index+1];
1799 float hm1 = m_elevationData->buffer()[index-1];
1800 d2h_dx2 = (hp1+hm1)/2 - elevation;
1801 dh_dx = (hp1-hm1)/2 / cache->resolution[0];
1803 if (j > 0 && j < height-1)
1805 float hp1 = m_elevationData->buffer()[index+width];
1806 float hm1 = m_elevationData->buffer()[index-width];
1807 d2h_dy2 = (hp1+hm1)/2 - elevation;
1808 dh_dy = (hp1-hm1)/2 / cache->resolution[1];
1811 // Minimise variance from original value
1812 if (m_weights[Variance] != 0.0f)
1814 // treat borderline pixels as reliable
1815 float reliability = cache->reliability;
1816 if (i < 5 || i >= width-5 || j < 5 || j >= height-5)
1818 reliability = 10.0f;
1820 costVariance += reliability * (elevation-cache->originalElevation)*(elevation-cache->originalElevation);
1821 ++countVariance;
1824 // Minimise roughness
1825 if (m_weights[Roughness] != 0.0f)
1827 costRoughness += (d2h_dx2*d2h_dx2 + d2h_dy2*d2h_dy2);
1828 ++countRoughness;
1831 // Also minimise slope
1832 // This works really well at getting a nice slope
1833 if (m_weights[Steepness] != 0.0f)
1835 costSteepness += (dh_dx*dh_dx + dh_dy*dh_dy);
1836 ++countSteepness;
1839 // Photometric stereo
1840 if (cache->normalEstimate[2] > 0.2f && m_weights[PhotometricStereo] != 0.0f)
1842 // (0.0f, -1.0f, dh_dy) x (1.0f, 0.0f, dh_dx)
1843 maths::Vector<3,float> norm(-dh_dx, dh_dy, 1.0f);
1844 norm.normalize();
1845 costStereo += MySqr(acos(cache->normalEstimate*norm));
1846 //costStereo += MySqr(dh_dx - cache->normalEstimate[0]/cache->normalEstimate[2]);
1847 //costStereo += MySqr(dh_dy - cache->normalEstimate[1]/cache->normalEstimate[2]);
1848 ++countStereo;
1851 // Per dataset
1852 for (int ds = 0; ds < m_numDatasets; ++ds)
1854 PerDatasetPixelCache* dsCache = &m_data[ds]->buffer()[index];
1856 bool isShadowed = (m_shadowData[ds]->sampleFloat((float)i/(width-1), (float)j/(height-1)) < 0.5f);
1857 bool isShadowEntrance = isShadowed &&
1858 dsCache->shadowTransition[TransitionEntrance][0] == i &&
1859 dsCache->shadowTransition[TransitionEntrance][1] == j;
1860 bool isShadowExit = isShadowed &&
1861 dsCache->shadowTransition[TransitionExit][0] == i &&
1862 dsCache->shadowTransition[TransitionExit][1] == j;
1864 // Calculate measures relating to the light direction
1865 // Gradient
1866 // Two sample points are 2*dsCache->lxy apart
1867 float hwm1 = m_elevationData->sampleFloat(
1868 ((float)i + dsCache->light[0]/cache->resolution[0])/(width-1),
1869 ((float)j - dsCache->light[1]/cache->resolution[1])/(height-1)
1871 float hwp1 = m_elevationData->sampleFloat(
1872 ((float)i - dsCache->light[0]/cache->resolution[0])/(width-1),
1873 ((float)j + dsCache->light[1]/cache->resolution[1])/(height-1)
1875 float dh_dw = (hwm1 - hwp1)/(2*dsCache->lxy);
1876 float d2h_dw2 = (hwm1+hwp1)/2 - elevation;
1877 float facingness = dsCache->dlz_dlxy-dh_dw;
1879 if (isShadowed)
1881 // maximum elevation is between elevation at entrance and elevation at exit
1882 if (m_weights[BelowShadowLine] != 0.0f &&
1883 dsCache->shadowTransition[TransitionExit][0] >= 0 &&
1884 dsCache->shadowTransition[TransitionExit][0] < width &&
1885 dsCache->shadowTransition[TransitionExit][1] >= 0 &&
1886 dsCache->shadowTransition[TransitionExit][1] < height &&
1887 dsCache->shadowTransition[TransitionEntrance][0] >= 0 &&
1888 dsCache->shadowTransition[TransitionEntrance][0] < width &&
1889 dsCache->shadowTransition[TransitionEntrance][1] >= 0 &&
1890 dsCache->shadowTransition[TransitionEntrance][1] < height)
1892 int exitIndex = width*dsCache->shadowTransition[TransitionExit][1] + dsCache->shadowTransition[TransitionExit][0];
1893 int entranceIndex = width*dsCache->shadowTransition[TransitionEntrance][1] + dsCache->shadowTransition[TransitionEntrance][0];
1894 float exitElevation = m_elevationData->buffer()[exitIndex];
1895 float entranceElevation = m_elevationData->buffer()[entranceIndex];
1897 costBelowShadowLine += MySqr(qMax(0.0f, elevation - (exitElevation * dsCache->shadowTransitionDistance[TransitionEntrance]
1898 +entranceElevation * dsCache->shadowTransitionDistance[TransitionExit])
1899 / (dsCache->shadowTransitionDistance[TransitionEntrance]
1900 +dsCache->shadowTransitionDistance[TransitionExit])));
1901 ++countBelowShadowLine;
1904 else
1906 if (facingness < 0.0f && m_weights[LitFacing] != 0.0f)
1908 // Lit pixels must face light
1909 costLitFacing += facingness*facingness;
1910 ++countLitFacing;
1912 // Simple shape from shading
1913 if (0 != m_shadingData[ds] && m_weights[Shading] != 0.0f)
1915 float intensity = m_shadingData[ds]->sampleFloat((float)i/(width-1), (float)j/(height-1));
1916 // intensity = cos(theta) = L.N
1917 // (0.0f, -1.0f, dh_dy) x (1.0f, 0.0f, dh_dx)
1918 maths::Vector<3,float> norm(-dh_dx, dh_dy, 1.0f);
1919 norm.normalize();
1920 costShading += MySqr(acos(qMin(1.0f, intensity)) - acos(qMin(1.0f, dsCache->light*norm)));
1921 ++countShading;
1924 if (isShadowEntrance)
1926 if (m_weights[TransitionElevTop] != 0.0f &&
1927 dsCache->shadowTransition[TransitionExit][0] >= 0 &&
1928 dsCache->shadowTransition[TransitionExit][0] < width &&
1929 dsCache->shadowTransition[TransitionExit][1] >= 0 &&
1930 dsCache->shadowTransition[TransitionExit][1] < height)
1932 int exitIndex = width*dsCache->shadowTransition[TransitionExit][1] + dsCache->shadowTransition[TransitionExit][0];
1933 float exitElevation = m_elevationData->buffer()[exitIndex];
1934 //if (dsCache->shadowTransitionDistance[TransitionExit]*dsCache->dlz_dlxy > 100.0f)
1936 costTransitionElevTop += MySqr(exitElevation + dsCache->shadowTransitionDistance[TransitionExit]*dsCache->dlz_dlxy - elevation);
1937 ++countTransitionElevTop;
1940 // gradient tangential to sun direction
1941 if (m_weights[TransitionTangential] != 0.0f)
1943 costTransitionTangential += MySqr(facingness);
1944 ++countTransitionTangential;
1946 // second derivitive should be negative at entrance
1947 if (m_weights[TransitionCurvingDown] != 0.0f)
1949 costTransitionCurvingDown += MySqr(qMax(0.0f, d2h_dw2));
1950 ++countTransitionCurvingDown;
1953 if (isShadowExit)
1955 if (m_weights[TransitionElevBottom] != 0.0f &&
1956 dsCache->shadowTransition[TransitionEntrance][0] >= 0 &&
1957 dsCache->shadowTransition[TransitionEntrance][0] < width &&
1958 dsCache->shadowTransition[TransitionEntrance][1] >= 0 &&
1959 dsCache->shadowTransition[TransitionEntrance][1] < height)
1961 int entranceIndex = width*dsCache->shadowTransition[TransitionEntrance][1] + dsCache->shadowTransition[TransitionEntrance][0];
1962 float entranceElevation = m_elevationData->buffer()[entranceIndex];
1963 // this has less effect, its usually "more" right
1964 /// @todo Smooth high reliability into low reliability
1965 costTransitionElevBottom += 1.0f * MySqr(entranceElevation - dsCache->shadowTransitionDistance[TransitionEntrance]*dsCache->dlz_dlxy - elevation);
1966 ++countTransitionElevBottom;
1973 return m_weights[Variance]*costVariance//countVariance
1974 + m_weights[Roughness]*costRoughness//countRoughness
1975 + m_weights[Steepness]*costSteepness//countSteepness
1976 + m_weights[LitFacing]*costLitFacing//countLitFacing
1977 + m_weights[Shading]*costShading//countShading
1978 + m_weights[PhotometricStereo]*costStereo//countStereo
1979 + m_weights[BelowShadowLine]*costBelowShadowLine//countBelowShadowLine
1980 + m_weights[TransitionElevTop]*costTransitionElevTop//countTransitionElevTop
1981 + m_weights[TransitionElevBottom]*costTransitionElevBottom//countTransitionElevBottom
1982 + m_weights[TransitionTangential]*costTransitionTangential//countTransitionTangential
1983 + m_weights[TransitionCurvingDown]*costTransitionCurvingDown//countTransitionCurvingDown
1988 * f(0) = 1
1989 * f(range) = small
1990 * f(x) = exp[ - (2*x/range)^2 ]
1992 inline float elevationFalloffFunction(float xsq)
1994 return qMax(0.0f, 1.0f - xsq);
1995 //return exp(-4*xsq);
2000 /// Calculate the relative cost of changing a pixel's elevation.
2001 float tcElevationOptimization::costChange(int x, int y, float dh, int range)
2003 // Override range for now
2004 #define ELEV_RANGE 4
2005 range = qMin(range, ELEV_RANGE);
2006 static const int effectiveRange = 1 + range;
2007 float oldElevations[1+2*ELEV_RANGE][1+2*ELEV_RANGE];
2009 // Assume a single change in elevation only affects cost of pixels to effectiveRange from it.
2010 // Now technically this doesn't hold for shadow edges as all pixels across the shadow can be affected.
2011 // If the pixel is a shadow edge, take into account the cost of extra pixels.
2012 float currentCost = cost(x-effectiveRange, y-effectiveRange, x+effectiveRange, y+effectiveRange);
2013 for (int i = 0; i < 1+range*2; ++i)
2015 int di = i - range;
2016 for (int j = 0; j < 1+range*2; ++j)
2018 int dj = j - range;
2019 float* elev = pixelElevation(x+di, y+dj);
2020 if (0 != elev && (!m_voidsOnly || 0 != m_datum->buffer()[j*m_datum->width() + i].reliability))
2022 oldElevations[i][j] = *elev;
2023 *elev += dh*elevationFalloffFunction(float(di*di + dj*dj) / ((range+1)*(range+1)));
2027 float newCost = cost(x-effectiveRange, y-effectiveRange, x+effectiveRange, y+effectiveRange);
2028 for (int i = 0; i < 1+range*2; ++i)
2030 int di = i - range;
2031 for (int j = 0; j < 1+range*2; ++j)
2033 int dj = j - range;
2034 float* elev = pixelElevation(x+di, y+dj);
2035 if (0 != elev && (!m_voidsOnly || 0 != m_datum->buffer()[j*m_datum->width() + i].reliability))
2037 *elev = oldElevations[i][j];
2041 return newCost - currentCost;
2044 /// Optimize a whole range of elevations.
2045 void tcElevationOptimization::optimizeElevations(int x1, int y1, int x2, int y2, float dh, int range)
2047 for (int j = y1; j <= y2; ++j)
2049 for (int i = x1; i <= x2; ++i)
2051 if (m_stopProcessing)
2053 return;
2055 int index = j*m_datum->width() + i;
2056 //while (true)
2057 if (!m_voidsOnly || 0 != m_datum->buffer()[j*m_datum->width() + i].reliability)
2059 // Find the cost of adjusting the elevation by dh in each direction
2060 float costInc = costChange(i,j, dh, range);
2061 float costDec = costChange(i,j,-dh, range);
2062 if (costInc < 0.0f && costInc < costDec)
2064 adjustElevation(i,j,dh, range);
2066 else if (costDec < 0.0f)
2068 adjustElevation(i,j,-dh, range);
2070 else
2072 //break;
2079 /// Get pointer to elevation at a pixel.
2080 float* tcElevationOptimization::pixelElevation(int x, int y) const
2082 if (x < 0 || x >= m_elevationData->width() ||
2083 y < 0 || y >= m_elevationData->height())
2085 return 0;
2087 int index = y*m_elevationData->width() + x;
2088 return &(m_elevationData->buffer()[index]);
2091 /// Adjust elevation at a pixel.
2092 void tcElevationOptimization::adjustElevation(int x, int y, float dh, int range)
2094 range = qMin(range, ELEV_RANGE);
2095 for (int i = 0; i < 1+range*2; ++i)
2097 int di = i - range;
2098 for (int j = 0; j < 1+range*2; ++j)
2100 int dj = j - range;
2101 float* elev = pixelElevation(x+di, y+dj);
2102 if (0 != elev)
2104 *elev += dh*elevationFalloffFunction(float(di*di + dj*dj) / ((range+1)*(range+1)));