-
Notifications
You must be signed in to change notification settings - Fork 168
/
BundleAdjust.cpp
3375 lines (2728 loc) · 116 KB
/
BundleAdjust.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/** This is free and unencumbered software released into the public domain.
The authors of ISIS do not claim copyright on the contents of this file.
For more details about the LICENSE terms and the AUTHORS, you will
find files of those names at the top level of this repository. **/
/* SPDX-License-Identifier: CC0-1.0 */
#include "BundleAdjust.h"
// std lib
#include <iomanip>
#include <iostream>
#include <sstream>
// qt lib
#include <QCoreApplication>
#include <QDebug>
#include <QFile>
#include <QMutex>
// boost lib
#include <boost/lexical_cast.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <boost/numeric/ublas/matrix_sparse.hpp>
#include <boost/numeric/ublas/vector_proxy.hpp>
// Isis lib
#include "Application.h"
#include "BundleObservation.h"
#include "BundleObservationSolveSettings.h"
#include "BundleResults.h"
#include "BundleSettings.h"
#include "BundleSolutionInfo.h"
#include "BundleTargetBody.h"
#include "Camera.h"
#include "CameraDetectorMap.h"
#include "CameraDistortionMap.h"
#include "CameraFocalPlaneMap.h"
#include "CameraGroundMap.h"
#include "Control.h"
#include "ControlPoint.h"
#include "CorrelationMatrix.h"
#include "Distance.h"
#include "ImageList.h"
#include "iTime.h"
#include "Latitude.h"
#include "Longitude.h"
#include "MaximumLikelihoodWFunctions.h"
#include "SpecialPixel.h"
#include "StatCumProbDistDynCalc.h"
#include "SurfacePoint.h"
#include "Target.h"
using namespace boost::numeric::ublas;
using namespace Isis;
namespace Isis {
/**
* Custom error handler for CHOLMOD.
* If CHOLMOD encounters an error then this will be called.
*
* @param status The CHOLMOD error status.
* @param file The name of the source code file where the error occured.
* @param lineNumber The line number in file where the error occured.
* @param message The error message.
*/
static void cholmodErrorHandler(int nStatus,
const char* file,
int nLineNo,
const char* message) {
QString errlog;
errlog = "SPARSE: ";
errlog += message;
PvlGroup gp(errlog);
gp += PvlKeyword("File",file);
gp += PvlKeyword("Line_Number", toString(nLineNo));
gp += PvlKeyword("Status", toString(nStatus));
// Application::Log(gp);
errlog += ". (See print.prt for details)";
// throw IException(IException::Unknown, errlog, _FILEINFO_);
}
/**
* Construct a BundleAdjust object from the given settings, control network file,
* and cube list.
*
* @param bundleSettings A shared pointer to the BundleSettings to be used.
* @param cnetFile The filename of the control network to be used.
* @param cubeList The list of filenames of the cubes to be adjusted.
* @param printSummary If summaries should be printed each iteration.
*/
BundleAdjust::BundleAdjust(BundleSettingsQsp bundleSettings,
const QString &cnetFile,
const QString &cubeList,
bool printSummary) {
m_abort = false;
Progress progress;
// initialize constructor dependent settings...
// m_printSummary, m_cleanUp, m_cnetFileName, m_controlNet,
// m_serialNumberList, m_bundleSettings
m_printSummary = printSummary;
m_cleanUp = true;
m_cnetFileName = cnetFile;
try {
m_controlNet = ControlNetQsp( new ControlNet(cnetFile, &progress,
bundleSettings->controlPointCoordTypeReports()) );
}
catch (IException &e) {
throw;
}
m_bundleResults.setOutputControlNet(m_controlNet);
m_serialNumberList = new SerialNumberList(cubeList);
m_bundleSettings = bundleSettings;
m_bundleTargetBody = bundleSettings->bundleTargetBody();
init(&progress);
}
/**
* Construct a BundleAdjust object with held cubes.
*
* @param bundleSettings A shared pointer to the BundleSettings to be used.
* @param cnetFile The filename of the control network to be used.
* @param cubeList The list of filenames of the cubes to be adjusted.
* @param heldList The list of filenames of the held cubes. Held cubes must be in both
* heldList and cubeList.
* @param printSummary If summaries should be printed each iteration.
*/
BundleAdjust::BundleAdjust(BundleSettingsQsp bundleSettings,
QString &cnetFile,
SerialNumberList &snlist,
bool printSummary) {
// initialize constructor dependent settings...
// m_printSummary, m_cleanUp, m_cnetFileName, m_controlNet,
// m_serialNumberList, m_bundleSettings
m_abort = false;
Progress progress;
m_printSummary = printSummary;
m_cleanUp = false;
m_cnetFileName = cnetFile;
try {
m_controlNet = ControlNetQsp( new ControlNet(cnetFile, &progress) );
}
catch (IException &e) {
throw;
}
m_bundleResults.setOutputControlNet(m_controlNet);
m_serialNumberList = &snlist;
m_bundleSettings = bundleSettings;
m_bundleTargetBody = bundleSettings->bundleTargetBody();
init();
}
/**
* Constructs a BundleAdjust object using a Control object.
* A new control network object will be created as a copy of the Control's control network.
*
* @param bundleSettings A shared pointer to the BundleSettings to be used.
* @param cnet The Control object whose control network will be copied.
* The Control will not be modified by the BundleAdjust.
* @param snlist A serial number list containing the cubes to be adjusted.
* @param printSummary If summaries should be printed each iteration.
*/
BundleAdjust::BundleAdjust(BundleSettingsQsp bundleSettings,
Control &cnet,
SerialNumberList &snlist,
bool printSummary) {
// initialize constructor dependent settings...
// m_printSummary, m_cleanUp, m_cnetFileName, m_controlNet,
// m_serialNumberList, m_bundleSettings
m_abort = false;
Progress progress;
m_printSummary = printSummary;
m_cleanUp = false;
m_cnetFileName = cnet.fileName();
try {
m_controlNet = ControlNetQsp( new ControlNet(cnet.fileName(), &progress) );
}
catch (IException &e) {
throw;
}
m_bundleResults.setOutputControlNet(m_controlNet);
m_serialNumberList = &snlist;
m_bundleSettings = bundleSettings;
m_bundleTargetBody = bundleSettings->bundleTargetBody();
init();
}
/**
* Constructs a BundleAdjust object using a ControlNet object.
* A copy of the ControlNet will be used.
*
* @param bundleSettings A shared pointer to the BundleSettings to be used.
* @param cnet The ControlNet that will be copied. The original ControlNet
* will not be modified.
* @param snlist A serial number list containing the cubes to be adjusted.
* @param printSummary If summaries should be printed each iteration.
*/
BundleAdjust::BundleAdjust(BundleSettingsQsp bundleSettings,
ControlNet &cnet,
SerialNumberList &snlist,
bool printSummary) {
// initialize constructor dependent settings...
// m_printSummary, m_cleanUp, m_cnetFileName, m_controlNet,
// m_serialNumberList, m_bundleSettings
m_abort = false;
m_printSummary = printSummary;
m_cleanUp = false;
m_cnetFileName = "";
try {
m_controlNet = ControlNetQsp( new ControlNet(cnet) );
}
catch (IException &e) {
throw;
}
m_bundleResults.setOutputControlNet(m_controlNet);
m_serialNumberList = &snlist;
m_bundleSettings = bundleSettings;
m_bundleTargetBody = bundleSettings->bundleTargetBody();
init();
}
/**
* Constructs a BundleAdjust from an already created ControlNet within a shared pointer.
*
* @param bundleSettings QSharedPointer to the bundle settings to use.
* @param cnet QSharedPointer to the control net to adjust.
* @param cubeList QString name of list of cubes to create serial numbers for.
* @param printSummary Boolean indicating whether to print application output summary.
*/
BundleAdjust::BundleAdjust(BundleSettingsQsp bundleSettings,
ControlNetQsp cnet,
const QString &cubeList,
bool printSummary) {
m_abort = false;
m_printSummary = printSummary;
m_cleanUp = false;
m_cnetFileName = "";
try {
m_controlNet = cnet;
}
catch (IException &e) {
throw;
}
m_bundleResults.setOutputControlNet(m_controlNet);
m_serialNumberList = new SerialNumberList(cubeList);
m_bundleSettings = bundleSettings;
m_bundleTargetBody = bundleSettings->bundleTargetBody();
init();
}
/**
* Thread safe constructor.
*
* @param bundleSettings A shared pointer to the BundleSettings to be used.
* @param control The Control object whose control network will be copied.
* The Control will not be modified by the BundleAdjust.
* @param snlist A serial number list containing the cubes to be adjusted.
* @param printSummary If summaries should be printed each iteration.
*/
BundleAdjust::BundleAdjust(BundleSettingsQsp bundleSettings,
Control &control,
QList<ImageList *> imgLists,
bool printSummary) {
m_bundleSettings = bundleSettings;
m_abort = false;
try {
m_controlNet = ControlNetQsp( new ControlNet(control.fileName()) );
}
catch (IException &e) {
throw;
}
m_bundleResults.setOutputControlNet(m_controlNet);
m_imageLists = imgLists;
// this is too slow and we need to get rid of the serial number list anyway
// should be unnecessary as Image class has serial number
// could hang on to image list until creating BundleObservations?
m_serialNumberList = new SerialNumberList;
foreach (ImageList *imgList, imgLists) {
foreach (Image *image, *imgList) {
m_serialNumberList->add(image->fileName());
// m_serialNumberList->add(image->serialNumber(), image->fileName());
}
}
m_bundleTargetBody = bundleSettings->bundleTargetBody();
m_printSummary = printSummary;
m_cleanUp = false;
m_cnetFileName = control.fileName();
init();
}
/**
* Destroys BundleAdjust object, deallocates pointers (if we have ownership),
* and frees variables from cholmod library.
*
* @internal
* @history 2016-10-13 Ian Humphrey - Removed deallocation of m_pHeldSnList, since this
* member was removed. References #4293.
*/
BundleAdjust::~BundleAdjust() {
// If we have ownership
if (m_cleanUp) {
delete m_serialNumberList;
}
freeCHOLMODLibraryVariables();
}
/**
* Initialize all solution parameters. This method is called
* by constructors to
* <ul>
* <li> initialize member variables </li>
* <li> set up the control net </li>
* <li> get the cameras set up for all images </li>
* <li> clear JigsawRejected flags </li>
* <li> create a new BundleImages and add to BundleObservation </li>
* <li> set up vector of BundleControlPoints </li>
* <li> set parent observation for each BundleMeasure </li>
* <li> use BundleSettings to set more parameters </li>
* <li> set up matrix initializations </li>
* <li> initialize cholomod library variables </li>
* </ul>
*
* @param progress A pointer to the progress of creating the cameras.
*
* @throws IException::Programmer "In BundleAdjust::init(): image is null."
* @throws IException::Programmer "In BundleAdjust::init(): observation is null."
*
* @internal
* @history 2011-08-14 Debbie A. Cook - Opt out of network validation
* for deltack network in order to allow
* a single measure on a point
* @history 2016-10-13 Ian Humphrey - Removed verification of held images in the from list
* and counting of the number of held images. References #4293.
*
* @todo remove printf statements
* @todo answer comments with questions, TODO, ???, and !!!
*/
void BundleAdjust::init(Progress *progress) {
emit(statusUpdate("Initialization"));
m_previousNumberImagePartials = 0;
// initialize
//
// JWB
// - some of these not originally initialized.. better values???
m_iteration = 0;
m_rank = 0;
m_iterationSummary = "";
// Get the cameras set up for all images
// NOTE - THIS IS NOT THE SAME AS "setImage" as called in BundleAdjust::computePartials
// this call only does initializations; sets measure's camera pointer, etc
// RENAME????????????
m_controlNet->SetImages(*m_serialNumberList, progress);
// clear JigsawRejected flags
m_controlNet->ClearJigsawRejected();
// initialize held variables
int numImages = m_serialNumberList->size();
// matrix stuff
m_normalInverse.clear();
m_RHS.clear();
m_imageSolution.clear();
// we don't want to call initializeCHOLMODLibraryVariables() here since mRank=0
// m_cholmodCommon, m_sparseNormals are not initialized
m_L = NULL;
m_cholmodNormal = NULL;
m_cholmodTriplet = NULL;
// should we initialize objects m_xResiduals, m_yResiduals, m_xyResiduals
// TESTING
// TODO: code below should go into a separate method???
// set up BundleObservations and assign solve settings for each from BundleSettings class
for (int i = 0; i < numImages; i++) {
Camera *camera = m_controlNet->Camera(i);
QString observationNumber = m_serialNumberList->observationNumber(i);
QString instrumentId = m_serialNumberList->spacecraftInstrumentId(i);
QString serialNumber = m_serialNumberList->serialNumber(i);
QString fileName = m_serialNumberList->fileName(i);
// create a new BundleImage and add to new (or existing if observation mode is on)
// BundleObservation
BundleImageQsp image = BundleImageQsp(new BundleImage(camera, serialNumber, fileName));
if (!image) {
QString msg = "In BundleAdjust::init(): image " + fileName + "is null." + "\n";
throw IException(IException::Programmer, msg, _FILEINFO_);
}
BundleObservationQsp observation =
m_bundleObservations.addNew(image, observationNumber, instrumentId, m_bundleSettings);
if (!observation) {
QString msg = "In BundleAdjust::init(): observation "
+ observationNumber + "is null." + "\n";
throw IException(IException::Programmer, msg, _FILEINFO_);
}
}
// initialize exterior orientation (spice) for all BundleImages in all BundleObservations
// TODO!!! - should these initializations just be done when we add the new observation above?
m_bundleObservations.initializeExteriorOrientation();
if (m_bundleSettings->solveTargetBody()) {
m_bundleObservations.initializeBodyRotation();
}
// set up vector of BundleControlPoints
int numControlPoints = m_controlNet->GetNumPoints();
for (int i = 0; i < numControlPoints; i++) {
ControlPoint *point = m_controlNet->GetPoint(i);
if (point->IsIgnored()) {
continue;
}
BundleControlPointQsp bundleControlPoint(new BundleControlPoint
(m_bundleSettings, point));
m_bundleControlPoints.append(bundleControlPoint);
// set parent observation for each BundleMeasure
int numMeasures = bundleControlPoint->size();
for (int j=0; j < numMeasures; j++) {
BundleMeasureQsp measure = bundleControlPoint->at(j);
QString cubeSerialNumber = measure->cubeSerialNumber();
BundleObservationQsp observation =
m_bundleObservations.observationByCubeSerialNumber(cubeSerialNumber);
BundleImageQsp image = observation->imageByCubeSerialNumber(cubeSerialNumber);
measure->setParentObservation(observation);
measure->setParentImage(image);
}
}
//===========================================================================================//
//==== Use the bundle settings to initialize more member variables and set up solutions =====//
//===========================================================================================//
// TODO: Need to have some validation code to make sure everything is
// on the up-and-up with the control network. Add checks for multiple
// networks, images without any points, and points on images removed from
// the control net (when we start adding software to remove points with high
// residuals) and ?. For "deltack" a single measure on a point is allowed
// so skip the test.
if (m_bundleSettings->validateNetwork()) {
validateNetwork();
}
m_bundleResults.maximumLikelihoodSetUp(m_bundleSettings->maximumLikelihoodEstimatorModels());
//===========================================================================================//
//=============== End Bundle Settings =======================================================//
//===========================================================================================//
//===========================================================================================//
//======================== initialize matrices and more parameters ==========================//
//===========================================================================================//
// size of reduced normals matrix
// TODO
// this should be determined from BundleSettings
// m_rank will be the sum of observation, target, and self-cal parameters
// TODO
m_rank = m_bundleObservations.numberParameters();
if (m_bundleSettings->solveTargetBody()) {
m_rank += m_bundleSettings->numberTargetBodyParameters();
if (m_bundleTargetBody->solveMeanRadius() || m_bundleTargetBody->solveTriaxialRadii()) {
outputBundleStatus("Warning: Solving for the target body radii (triaxial or mean) "
"is NOT possible and likely increases error in the solve.\n");
}
if (m_bundleTargetBody->solveMeanRadius()){
// Check if MeanRadiusValue is abnormal compared to observation
bool isMeanRadiusValid = true;
double localRadius, aprioriRadius;
// Arbitrary control point containing an observed localRadius
BundleControlPointQsp point = m_bundleControlPoints.at(0);
SurfacePoint surfacepoint = point->adjustedSurfacePoint();
localRadius = surfacepoint.GetLocalRadius().meters();
aprioriRadius = m_bundleTargetBody->meanRadius().meters();
// Ensure aprioriRadius is within some threshold
// Considered potentially inaccurate if it's off by atleast a factor of two
if (aprioriRadius >= 2 * localRadius || aprioriRadius <= localRadius / 2) {
isMeanRadiusValid = false;
}
// Warn user for abnormal MeanRadiusValue
if (!isMeanRadiusValid) {
outputBundleStatus("Warning: User-entered MeanRadiusValue appears to be inaccurate. "
"This can cause a bundle failure.\n");
}
}
}
int num3DPoints = m_bundleControlPoints.size();
m_bundleResults.setNumberUnknownParameters(m_rank + 3 * num3DPoints);
m_imageSolution.resize(m_rank);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// initializations for cholmod
initializeCHOLMODLibraryVariables();
// initialize normal equations matrix
initializeNormalEquationsMatrix();
}
/**
* control network validation - on the very real chance that the net
* has not been checked before running the bundle
*
* checks implemented for ...
* (1) images with 0 or 1 measures
*
* @return @b bool If the control network is valid.
*
* @throws IException::User "Images with one or less measures:"
*
* @internal
* @history 2011-08-04 Debbie A. Cook - Changed error message to
* indicate it fails with one measure as
* well as no measures.
* @history 2016-09-22 Ian Humphrey - Replaced statusUpdate signal emits with direct
* calls to outputBundleStats() so the validation messages are
* printed to stdout. References #4313.
*/
bool BundleAdjust::validateNetwork() {
outputBundleStatus("\nValidating network...");
int imagesWithInsufficientMeasures = 0;
QString msg = "Images with one or less measures:\n";
int numObservations = m_bundleObservations.size();
for (int i = 0; i < numObservations; i++) {
int numImages = m_bundleObservations.at(i)->size();
for (int j = 0; j < numImages; j++) {
BundleImageQsp bundleImage = m_bundleObservations.at(i)->at(j);
int numMeasures = m_controlNet->
GetNumberOfValidMeasuresInImage(bundleImage->serialNumber());
if (numMeasures > 1) {
continue;
}
imagesWithInsufficientMeasures++;
msg += bundleImage->fileName() + ": " + toString(numMeasures) + "\n";
}
}
if ( imagesWithInsufficientMeasures > 0 ) {
throw IException(IException::User, msg, _FILEINFO_);
}
outputBundleStatus("\nValidation complete!...\n");
return true;
}
/**
* Initializations for CHOLMOD sparse matrix package.
* Calls cholmod_start, sets m_cholmodCommon options.
*
* @return @b bool If the CHOLMOD library variables were successfully initialized.
*/
bool BundleAdjust::initializeCHOLMODLibraryVariables() {
if ( m_rank <= 0 ) {
return false;
}
m_cholmodTriplet = NULL;
cholmod_start(&m_cholmodCommon);
// set user-defined cholmod error handler
m_cholmodCommon.error_handler = cholmodErrorHandler;
// testing not using metis
m_cholmodCommon.nmethods = 1;
m_cholmodCommon.method[0].ordering = CHOLMOD_AMD;
return true;
}
/**
* Initialize Normal Equations matrix (m_sparseNormals).
*
* Ken NOTE: Currently we are explicitly setting the start column for each block in the normal
* equations matrix below. I think it should be possible (and relatively easy) to make
* the m_sparseNormals smart enough to set the start column of a column block
* automatically when it is added to the matrix.
*
* @return @b bool.
*/
bool BundleAdjust::initializeNormalEquationsMatrix() {
int nBlockColumns = m_bundleObservations.size();
if (m_bundleSettings->solveTargetBody())
nBlockColumns += 1;
m_sparseNormals.setNumberOfColumns(nBlockColumns);
m_sparseNormals.at(0)->setStartColumn(0);
int nParameters = 0;
if (m_bundleSettings->solveTargetBody()) {
nParameters += m_bundleSettings->numberTargetBodyParameters();
m_sparseNormals.at(1)->setStartColumn(nParameters);
int observation = 0;
for (int i = 2; i < nBlockColumns; i++) {
nParameters += m_bundleObservations.at(observation)->numberParameters();
m_sparseNormals.at(i)->setStartColumn(nParameters);
observation++;
}
}
else {
for (int i = 0; i < nBlockColumns; i++) {
m_sparseNormals.at(i)->setStartColumn(nParameters);
nParameters += m_bundleObservations.at(i)->numberParameters();
}
}
return true;
}
/**
* @brief Free CHOLMOD library variables.
*
* Frees m_cholmodTriplet, m_cholmodNormal, and m_L.
* Calls cholmod_finish when complete.
*
* @return @b bool If the CHOLMOD library successfully cleaned up.
*/
bool BundleAdjust::freeCHOLMODLibraryVariables() {
cholmod_free_triplet(&m_cholmodTriplet, &m_cholmodCommon);
cholmod_free_sparse(&m_cholmodNormal, &m_cholmodCommon);
cholmod_free_factor(&m_L, &m_cholmodCommon);
cholmod_finish(&m_cholmodCommon);
return true;
}
/**
* Compute the least squares bundle adjustment solution using Cholesky decomposition.
*
* @return @b BundleSolutionInfo A container with settings and results from the adjustment.
*
* @see BundleAdjust::solveCholesky
*
* @TODO make solveCholesky return a BundleSolutionInfo object and delete this placeholder ???
*/
BundleSolutionInfo* BundleAdjust::solveCholeskyBR() {
solveCholesky();
return bundleSolveInformation();
}
/**
* Flag to abort when bundle is threaded. Flag is set outside the bundle thread,
* typically by the gui thread.
*/
void BundleAdjust::abortBundle() {
m_abort = true;
}
/**
* Compute the least squares bundle adjustment solution using Cholesky decomposition.
*
* @return @b bool If the solution was successfully computed.
*
* @internal
* @history 2016-10-25 Ian Humphrey - Spacing and precision for Sigma0 and Elapsed Time match
* ISIS production's jigsaw std output. References #4463."
* @history 2016-10-28 Ian Humphrey - Updated spacing for Error Propagation Complete message.
* References #4463."
* @history 2016-11-16 Ian Humphrey - Modified catch block to throw the caught exception, so
* a message box will appear to the user when running jigsaw in GUI
* mode. Fixes #4483.
*/
bool BundleAdjust::solveCholesky() {
emit(statusBarUpdate("Solving"));
try {
// throw error if a frame camera is included AND
// if m_bundleSettings->solveInstrumentPositionOverHermiteSpline()
// is set to true (can only use for line scan or radar)
// if (m_bundleSettings->solveInstrumentPositionOverHermiteSpline() == true) {
// int numImages = images();
// for (int i = 0; i < numImages; i++) {
// if (m_controlNet->Camera(i)->GetCameraType() == 0) {
// QString msg = "At least one sensor is a frame camera. "
// "Spacecraft Option OVERHERMITE is not valid for frame cameras\n";
// throw IException(IException::User, msg, _FILEINFO_);
// }
// }
// }
// Compute the apriori coordinates for each nonheld point
m_controlNet->ComputeApriori(); // original location
// ken testing - if solving for target mean radius, set point radius to current mean radius
// if solving for triaxial radii, set point radius to local radius
if (m_bundleTargetBody && m_bundleTargetBody->solveMeanRadius()) {
int numControlPoints = m_bundleControlPoints.size();
for (int i = 0; i < numControlPoints; i++) {
BundleControlPointQsp point = m_bundleControlPoints.at(i);
SurfacePoint surfacepoint = point->adjustedSurfacePoint();
surfacepoint.ResetLocalRadius(m_bundleTargetBody->meanRadius());
point->setAdjustedSurfacePoint(surfacepoint);
}
}
// Only use target body solution options when using Latitudinal coordinates
if (m_bundleTargetBody && m_bundleTargetBody->solveTriaxialRadii()
&& m_bundleSettings->controlPointCoordTypeBundle() == SurfacePoint::Latitudinal) {
int numControlPoints = m_bundleControlPoints.size();
for (int i = 0; i < numControlPoints; i++) {
BundleControlPointQsp point = m_bundleControlPoints.at(i);
SurfacePoint surfacepoint = point->adjustedSurfacePoint();
Distance localRadius = m_bundleTargetBody->localRadius(surfacepoint.GetLatitude(),
surfacepoint.GetLongitude());
surfacepoint.ResetLocalRadius(localRadius);
point->setAdjustedSurfacePoint(surfacepoint);
}
}
// Beginning of iterations
m_iteration = 1;
double vtpv = 0.0;
double previousSigma0 = 0.0;
// start the clock
clock_t solveStartClock = clock();
for (;;) {
emit iterationUpdate(m_iteration);
// testing
if (m_abort) {
m_bundleResults.setConverged(false);
emit statusUpdate("\n aborting...");
emit finished();
return false;
}
// testing
emit statusUpdate( QString("starting iteration %1\n").arg(m_iteration) );
clock_t iterationStartClock = clock();
// zero normals (after iteration 0)
if (m_iteration != 1) {
m_sparseNormals.zeroBlocks();
}
// form normal equations -- computePartials is called in here.
if (!formNormalEquations()) {
m_bundleResults.setConverged(false);
break;
}
// testing
if (m_abort) {
m_bundleResults.setConverged(false);
emit statusUpdate("\n aborting...");
emit finished();
return false;
}
// testing
// solve the system
if (!solveSystem()) {
outputBundleStatus("\nsolve failed!");
m_bundleResults.setConverged(false);
break;
}
// testing
if (m_abort) {
m_bundleResults.setConverged(false);
emit statusUpdate("\n aborting...");
emit finished();
return false;
}
// testing
// apply parameter corrections
applyParameterCorrections();
// testing
if (m_abort) {
m_bundleResults.setConverged(false);
emit statusUpdate("\n aborting...");
emit finished();
return false;
}
// testing
// compute residuals
vtpv = computeResiduals();
// flag outliers
if ( m_bundleSettings->outlierRejection() ) {
computeRejectionLimit();
flagOutliers();
}
// testing
if (m_abort) {
m_bundleResults.setConverged(false);
emit statusUpdate("\n aborting...");
emit finished();
return false;
}
// testing
// variance of unit weight (also reference variance, variance factor, etc.)
m_bundleResults.computeSigma0(vtpv, m_bundleSettings->convergenceCriteria());
// Set up formatting for status updates with doubles (e.g. Sigma0, Elapsed Time)
int fieldWidth = 20;
char format = 'f';
int precision = 10;
emit statusUpdate(QString("Iteration: %1 \n")
.arg(m_iteration));
emit statusUpdate(QString("Sigma0: %1 \n")
.arg(m_bundleResults.sigma0(),
fieldWidth,
format,
precision));
emit statusUpdate(QString("Observations: %1 \n")
.arg(m_bundleResults.numberObservations()));
emit statusUpdate(QString("Constrained Parameters:%1 \n")
.arg(m_bundleResults.numberConstrainedPointParameters()));
emit statusUpdate(QString("Unknowns: %1 \n")
.arg(m_bundleResults.numberUnknownParameters()));
emit statusUpdate(QString("Degrees of Freedom: %1 \n")
.arg(m_bundleResults.degreesOfFreedom()));
emit iterationUpdate(m_iteration);
// check for convergence
if (m_bundleSettings->convergenceCriteria() == BundleSettings::Sigma0) {
if (fabs(previousSigma0 - m_bundleResults.sigma0())
<= m_bundleSettings->convergenceCriteriaThreshold()) {
// convergence detected
// if maximum likelihood tiers are being processed,
// check to see if there's another tier to go.
if (m_bundleResults.maximumLikelihoodModelIndex()
< m_bundleResults.numberMaximumLikelihoodModels() - 1
&& m_bundleResults.maximumLikelihoodModelIndex()
< 2) {
// TODO is this second condition redundant???
// should BundleResults require num models <= 3, so num models - 1 <= 2
if (m_bundleResults.numberMaximumLikelihoodModels()
> m_bundleResults.maximumLikelihoodModelIndex() + 1) {
// If there is another tier left we will increment the index.
m_bundleResults.incrementMaximumLikelihoodModelIndex();
}
}
else { // otherwise iterations are complete
m_bundleResults.setConverged(true);
emit statusUpdate("Bundle has converged\n");
emit statusBarUpdate("Converged");
break;
}
}
}
else {
// bundleSettings.convergenceCriteria() == BundleSettings::ParameterCorrections
int numConvergedParams = 0;
int numImgParams = m_imageSolution.size();
for (int ij = 0; ij < numImgParams; ij++) {
if (fabs(m_imageSolution(ij)) > m_bundleSettings->convergenceCriteriaThreshold()) {
break;
}
else
numConvergedParams++;
}
if ( numConvergedParams == numImgParams ) {
m_bundleResults.setConverged(true);
emit statusUpdate("Bundle has converged\n");
emit statusBarUpdate("Converged");
break;
}
}
m_bundleResults.printMaximumLikelihoodTierInformation();
clock_t iterationStopClock = clock();
double iterationTime = (iterationStopClock - iterationStartClock)
/ (double)CLOCKS_PER_SEC;
emit statusUpdate( QString("End of Iteration %1 \n").arg(m_iteration) );
emit statusUpdate( QString("Elapsed Time: %1 \n").arg(iterationTime,
fieldWidth,
format,
precision) );
// check for maximum iterations
if (m_iteration >= m_bundleSettings->convergenceCriteriaMaximumIterations()) {
emit(statusBarUpdate("Max Iterations Reached"));
break;
}
// restart the dynamic calculation of the cumulative probility distribution of residuals
// (in unweighted pixels) --so it will be up to date for the next iteration
if (!m_bundleResults.converged()) {
m_bundleResults.initializeResidualsProbabilityDistribution(101);
}
// TODO: is this necessary ???
// probably all ready initialized to 101 nodes in bundle settings constructor...
// if we're using CHOLMOD and still going, release cholmod_factor
// (if we don't, memory leaks will occur), otherwise we need it for error propagation
if (!m_bundleResults.converged() || !m_bundleSettings->errorPropagation()) {
cholmod_free_factor(&m_L, &m_cholmodCommon);
}
iterationSummary();
m_iteration++;
previousSigma0 = m_bundleResults.sigma0();
}
if (m_bundleResults.converged() && m_bundleSettings->errorPropagation()) {
clock_t errorPropStartClock = clock();
outputBundleStatus("\nStarting Error Propagation");
errorPropagation();
emit statusUpdate("\n\nError Propagation Complete\n");
clock_t errorPropStopClock = clock();
m_bundleResults.setElapsedTimeErrorProp((errorPropStopClock - errorPropStartClock)
/ (double)CLOCKS_PER_SEC);
}
clock_t solveStopClock = clock();
m_bundleResults.setElapsedTime((solveStopClock - solveStartClock)
/ (double)CLOCKS_PER_SEC);