#include "IsisDebug.h" #include "ControlPoint.h" #include <boost/numeric/ublas/symmetric.hpp> #include <boost/numeric/ublas/io.hpp> #include <QDebug> #include <QHash> #include <QString> #include <QStringList> #include "Application.h" #include "CameraDetectorMap.h" #include "CameraDistortionMap.h" #include "CameraFocalPlaneMap.h" #include "CameraGroundMap.h" #include "ControlMeasure.h" #include "ControlMeasureLogData.h" #include "ControlNet.h" #include "Cube.h" #include "IString.h" #include "Latitude.h" #include "Longitude.h" #include "PvlObject.h" #include "SerialNumberList.h" #include "SpecialPixel.h" #include "Statistics.h" using boost::numeric::ublas::symmetric_matrix; using boost::numeric::ublas::upper; using namespace std; namespace Isis { /** * Construct a control point * * @author tsucharski (5/5/2010) * */ ControlPoint::ControlPoint() : invalid(false) { measures = NULL; cubeSerials = NULL; measures = new QHash< QString, ControlMeasure * >; cubeSerials = new QStringList; type = Free; dateTime = ""; editLock = false; ignore = false; jigsawRejected = false; referenceExplicitlySet = false; aprioriSurfacePointSource = SurfacePointSource::None; aprioriRadiusSource = RadiusSource::None; parentNetwork = NULL; referenceMeasure = NULL; numberOfRejectedMeasures = 0; constraintStatus.reset(); } /** * Copy the given control point into this instance. * * @param other The control point to duplicate */ ControlPoint::ControlPoint(const ControlPoint &other) { measures = NULL; cubeSerials = NULL; referenceMeasure = NULL; parentNetwork = NULL; editLock = false; measures = new QHash< QString, ControlMeasure * >; cubeSerials = new QStringList; QListIterator<QString> i(*other.cubeSerials); while (i.hasNext()) { QString sn = i.next(); const ControlMeasure *otherCm = other.GetMeasure(sn); ControlMeasure *newMeasure = new ControlMeasure(*otherCm); AddMeasure(newMeasure); if (other.referenceMeasure == otherCm) { SetRefMeasure(newMeasure); } } id = other.id; chooserName = other.chooserName; dateTime = other.dateTime; type = other.type; invalid = other.invalid; editLock = other.editLock; jigsawRejected = other.jigsawRejected; referenceExplicitlySet = other.referenceExplicitlySet; ignore = other.ignore; aprioriSurfacePointSource = other.aprioriSurfacePointSource; aprioriSurfacePointSourceFile = other.aprioriSurfacePointSourceFile; aprioriRadiusSource = other.aprioriRadiusSource; aprioriRadiusSourceFile = other.aprioriRadiusSourceFile; aprioriSurfacePoint = other.aprioriSurfacePoint; adjustedSurfacePoint = other.adjustedSurfacePoint; numberOfRejectedMeasures = other.numberOfRejectedMeasures; constraintStatus = other.constraintStatus; } /** * Construct a control point with given Id * * @param id Control Point Id */ ControlPoint::ControlPoint(const QString &newId) : invalid(false) { parentNetwork = NULL; measures = NULL; referenceMeasure = NULL; numberOfRejectedMeasures = 0; measures = new QHash< QString, ControlMeasure * >; cubeSerials = new QStringList; id = newId; type = Free; editLock = false; jigsawRejected = false; referenceExplicitlySet = false; ignore = false; aprioriSurfacePointSource = SurfacePointSource::None; aprioriRadiusSource = RadiusSource::None; constraintStatus.reset(); } /** * This destroys the current instance and cleans up any and all allocated * memory. */ ControlPoint::~ControlPoint() { if (measures != NULL) { QList< QString > keys = measures->keys(); for (int i = 0; i < keys.size(); i++) { delete(*measures)[keys[i]]; (*measures)[keys[i]] = NULL; } delete measures; measures = NULL; } if (cubeSerials) { delete cubeSerials; cubeSerials = NULL; } referenceMeasure = NULL; } /** * Loads the PvlObject into a ControlPoint * * @param p PvlObject containing ControlPoint information * @param forceBuild Allows invalid Control Measures to be added to this * Control Point * * @throws Isis::iException::User - Invalid Point Type * @throws Isis::iException::User - Unable to add ControlMeasure to Control * Point * * @history 2008-06-18 Tracie Sucharski/Jeannie Walldren, Fixed bug with * checking for "True" vs "true", change to * lower case for comparison. * @history 2009-12-29 Tracie Sucharski - Added new ControlPoint information. * @history 2010-01-13 Tracie Sucharski - Changed from Set methods to simply * setting private variables to increase speed? * @history 2010-07-30 Tracie Sucharski, Updated for changes made after * additional working sessions for Control network * design. * @history 2010-09-01 Tracie Sucharski, Add checks for AprioriLatLonSource * AprioriLatLonSourceFile. If there are * AprioriSigmas,but no AprioriXYZ, use the XYZ values. * @history 2010-09-15 Tracie Sucharski, It was decided after mtg with * Debbie, Stuart, Ken and Tracie that ControlPoint * will only function with x/y/z, not lat/lon/radius. * It will be the responsibility of the application * or class using ControlPoint to set up a * SurfacePoint object to do conversions between x/y/z * and lat/lon/radius. * So... remove all conversion methods from this * class. * It was also decided that when importing old * networks that contain Sigmas, the sigmas will not * be imported , due to conflicts with the units of * the sigmas,we cannot get accurate x,y,z sigmas from * the lat,lon,radius sigmas without the covariance * matrix. * @history 2010-09-28 Tracie Sucharski, Added back the conversion methods * from lat,lon,radius to x,y,z only for the point, * since that is what most applications need. * @history 2010-12-02 Debbie A. Cook, Added units to * SurfacePoint.SetSpherical calls. * @history 2011-03-12 Debbie A. Cook, Added targetRadius to do conversions */ void ControlPoint::Load(PvlObject &p) { } /** * Add a measurement to the control point, taking ownership of the measure in * the process. * * @param measure The ControlMeasure to add */ void ControlPoint::Add(ControlMeasure *measure) { PointModified(); AddMeasure(measure); } /** * Do the actual work of adding a measure to this point, without changing * any extra data. */ void ControlPoint::AddMeasure(ControlMeasure *measure) { // Make sure measure is unique foreach(ControlMeasure * m, measures->values()) { if (m->GetCubeSerialNumber() == measure->GetCubeSerialNumber()) { QString msg = "The SerialNumber is not unique. A measure with " "serial number [" + measure->GetCubeSerialNumber() + "] already " "exists for ControlPoint [" + GetId() + "]"; throw IException(IException::Programmer, msg, _FILEINFO_); } } if (!measures->size()) { ASSERT(!HasRefMeasure()); referenceMeasure = measure; } else if (referenceMeasure->IsIgnored() && !measure->IsIgnored() && !IsReferenceExplicit() && !IsEditLocked()) { // The current "implicit" reference is ignored, but this new measure // isn't, and the point is not edit locked, so make this measure the new // reference referenceMeasure = measure; } measure->parentPoint = this; QString newSerial = measure->GetCubeSerialNumber(); measures->insert(newSerial, measure); cubeSerials->append(newSerial); // notify parent network if we have one if (parentNetwork) { parentNetwork->measureAdded(measure); parentNetwork->emitNetworkStructureModified(); } } /** * Throws an exception if none of the point's measures have the given serial * number. It is common to ensure that a measure exists before taking some * action. * * @param sn The serial number of the measure to validate */ void ControlPoint::ValidateMeasure(QString serialNumber) const { if (!measures->contains(serialNumber)) { QString msg = "No measure with serial number [" + serialNumber + "] is owned by this point"; throw IException(IException::Programmer, msg, _FILEINFO_); } } /** * Remove a measurement from the control point, deleting reference measure * is allowed. * * @param serialNumber The serial number of the measure to delete */ int ControlPoint::Delete(QString serialNumber) { ValidateMeasure(serialNumber); ControlMeasure *cm = (*measures)[serialNumber]; // notify parent network of the change if (parentNetwork) { parentNetwork->measureDeleted(cm); if (!IsIgnored() && !cm->IsIgnored()) { parentNetwork->emitNetworkStructureModified(); } } if (cm->IsEditLocked()) { return ControlMeasure::MeasureLocked; } // remove measure from the point's data structures measures->remove(serialNumber); cubeSerials->removeAt(cubeSerials->indexOf(serialNumber)); // update the reference measure if (cubeSerials->size()) { if (referenceMeasure == cm) { referenceMeasure = (*measures)[cubeSerials->at(0)]; referenceExplicitlySet = false; } } else { referenceMeasure = NULL; } delete cm; cm = NULL; PointModified(); return ControlMeasure::Success; } /** * This method is a wrapper to emit the measureModified() signal in the parent network * is called whenever a change is made to a Control Measure. * * @param measure The ControlMeasure* that was modified. * @param type The ControlMeasure::ModType indicating which modification occured. * @param oldValue The oldValue before the change. * @param newValue The new value that the change incorporated. */ void ControlPoint::emitMeasureModified(ControlMeasure *measure, ControlMeasure::ModType modType, QVariant oldValue, QVariant newValue) { if (parentNetwork) { parentNetwork->emitMeasureModified(measure, modType, oldValue, newValue); } } /** * Remove a measurement from the control point, deleting reference measure * is allowed. * * @param measure The measure to delete */ int ControlPoint::Delete(ControlMeasure *measure) { ASSERT(measure); return Delete(measure->GetCubeSerialNumber()); } /** * Remove a measurement from the control point, deleting reference measure * is allowed. * * @param index The index of the control measure to delete */ int ControlPoint::Delete(int index) { if (index < 0 || index >= cubeSerials->size()) { QString msg = "index [" + QString(index) + "] out of bounds"; throw IException(IException::Programmer, msg, _FILEINFO_); } return Delete(cubeSerials->at(index)); } /** * Reset all the Apriori info to defaults * * @author Sharmila Prasad (10/22/2010) */ ControlPoint::Status ControlPoint::ResetApriori() { if (IsEditLocked()) { return PointLocked; } aprioriSurfacePointSource = SurfacePointSource::None; aprioriSurfacePointSourceFile = ""; aprioriRadiusSource = RadiusSource::None; aprioriRadiusSourceFile = ""; aprioriSurfacePoint = SurfacePoint(); constraintStatus.reset(); return Success; } /** * Get a control measure based on its cube's serial number. * * @param serialNumber serial number of measure to get * @returns control measure with matching serial number */ ControlMeasure *ControlPoint::GetMeasure(QString serialNumber) { ValidateMeasure(serialNumber); return (*measures)[serialNumber]; } /** * Get a control measure based on its cube's serial number. * * @param serialNumber serial number of measure to get * @returns const control measure with matching serial number */ const ControlMeasure *ControlPoint::GetMeasure(QString serialNumber) const { ValidateMeasure(serialNumber); return measures->value(serialNumber); } const ControlMeasure *ControlPoint::GetMeasure(int index) const { if (index < 0 || index >= cubeSerials->size()) { QString msg = "Index [" + toString(index) + "] out of range"; throw IException(IException::Programmer, msg, _FILEINFO_); } return GetMeasure(cubeSerials->at(index)); } ControlMeasure *ControlPoint::GetMeasure(int index) { if (index < 0 || index >= cubeSerials->size()) { QString msg = "Index [" + toString(index) + "] out of range"; throw IException(IException::Programmer, msg, _FILEINFO_); } return GetMeasure(cubeSerials->at(index)); } /** * Checks to see if a reference measure is set. * * @returns bool True if a reference measure is set. */ bool ControlPoint::HasRefMeasure() const { return !(referenceMeasure == NULL); } /** * Get the reference control measure. * * @returns const reference measure for this point */ const ControlMeasure *ControlPoint::GetRefMeasure() const { if (!HasRefMeasure()) { QString msg = "Control point [" + GetId() + "] has no reference measure!"; throw IException(IException::Programmer, msg, _FILEINFO_); } return referenceMeasure; } /** * Get the measure that is the reference directly. */ ControlMeasure *ControlPoint::GetRefMeasure() { if (!HasRefMeasure()) { QString msg = "Control point [" + GetId() + "] has no reference measure!"; throw IException(IException::Programmer, msg, _FILEINFO_); } return referenceMeasure; } /** * Set the point's chooser name. This will be lost if any attributes relating * to this point is later changed and the current user will be set. This is * one of the 'last modified attributes' referred to in other comments. * * @param name The username of the person who last modified this control point */ ControlPoint::Status ControlPoint::SetChooserName(QString name) { if (editLock) { return PointLocked; } chooserName = name; return Success; } /** * Set the point's last modified time. This will be lost if any attributes * relating to this point are later changed and the current time will be * set. This is one of the 'last modified attributes' referred to in other * comments. * * @param newDateTime The date and time this control point was last modified */ ControlPoint::Status ControlPoint::SetDateTime(QString newDateTime) { if (editLock) { return PointLocked; } dateTime = newDateTime; return Success; } /** * Set the EditLock state. If edit lock is on, then most attributes relating * to this point are not modifiable. Edit lock is like "Don't modify my * attributes, but you can still modify my measures' attributes". The * reference measure is implicitely edit locked if the point is edit locked. * * @param lock True to enable edit lock, false to disable it and allow the * point to be modified. */ ControlPoint::Status ControlPoint::SetEditLock(bool lock) { if (parentNetwork) { parentNetwork->emitPointModified(this, ControlPoint::EditLockModified, editLock, lock); } editLock = lock; return Success; } /** * Set the jigsawRejected state. If IsRejected is true, then this point should be * ignored until the next iteration in the bundle adjustement. BundleAdjust * decides when to reject or accept a point. The initial IsRejected state of * a measure is false. * * @param reject True to reject a measure, false to include it in the adjustment */ ControlPoint::Status ControlPoint::SetRejected(bool reject) { jigsawRejected = reject; return Success; } /** * Sets the Id of the control point * * @param id Control Point Id * * @return (int) status Success or PointLocked */ ControlPoint::Status ControlPoint::SetId(QString newId) { if (editLock) { return PointLocked; } QString oldId = id; id = newId; if (parentNetwork) { parentNetwork->UpdatePointReference(this, oldId); } return Success; } /** * Set the point's reference measure * * @param cm The new reference measure */ ControlPoint::Status ControlPoint::SetRefMeasure(ControlMeasure *cm) { if (editLock) { return PointLocked; } ASSERT(cm); SetExplicitReference(cm); return Success; } /** * Set the point's reference measure * * @param index The index of the new reference measure */ ControlPoint::Status ControlPoint::SetRefMeasure(int index) { if (editLock) { return PointLocked; } if (index < 0 || index >= cubeSerials->size()) { QString msg = "Index ["; msg += toString(index) + "] out of range"; throw IException(IException::Programmer, msg, _FILEINFO_); } SetExplicitReference((*measures)[cubeSerials->at(index)]); return Success; } /** * Set the points reference measure * * @param sn The serial number of the new reference measure */ ControlPoint::Status ControlPoint::SetRefMeasure(QString sn) { if (editLock) { return PointLocked; } if (!cubeSerials->contains(sn)) { QString msg = "Point [" + id + "] has no measure with serial number [" + sn + "]"; throw IException(IException::Programmer, msg, _FILEINFO_); } SetExplicitReference((*measures)[sn]); return Success; } /** * Explicitly defines a new reference measure by pointer. This assumes the * point already has ownership over this pointer. As part of the explicit * definition process, the reference will attempt to be made ignored if the * measure will allow it. * * In the past, setting an explicit reference would also attempt to set the * new reference to un-ignored (this would only fail if the measure was "Edit * Locked"). This blanket rule was removed, however, because the bundle * adjustment processing phase could often intentionally set references to * ignored, and in some instances (e.g., merging a partial network back into * the base network) this rule would mistakenly set those properly ignored * references back to un-ignored. While this rule made sense for the * registration phase of processing, it clearly caused problems during bundle * adjustment and merging. * * @param measure The new reference measure */ void ControlPoint::SetExplicitReference(ControlMeasure *measure) { referenceExplicitlySet = true; referenceMeasure = measure; } /** * Set whether to ignore or use control point * * @param newIgnoreStatus True to ignore this Control Point, False to * un-ignore */ ControlPoint::Status ControlPoint::SetIgnored(bool newIgnoreStatus) { if (editLock) { return PointLocked; } bool oldStatus = ignore; ignore = newIgnoreStatus; // only update if there was a change in status if (oldStatus != ignore) { PointModified(); if (parentNetwork) { if (ignore) { parentNetwork->pointIgnored(this); } else { parentNetwork->pointUnIgnored(this); } parentNetwork->emitPointModified(this, ControlPoint::IgnoredModified, oldStatus, ignore); } } return Success; } /** * Set or update the surface point relating to this control point. This is * the point on the surface of the planet that the measures are tied to. * This updates the last modified attributes of this point. * *** Warning: Only BundleAdjust and its applications should be * using this method. * * @param newSurfacePoint The point on the target's surface the measures are * tied to * * @internal * @history 2011-07-01 Debbie A. Cook Removed editLock check */ ControlPoint::Status ControlPoint::SetAdjustedSurfacePoint( SurfacePoint newSurfacePoint) { PointModified(); adjustedSurfacePoint = newSurfacePoint; return Success; } /** * Updates the control point's type. This updates the last modified attributes * of this point. * * @see PointType * * @param newType The new type this control point should be */ ControlPoint::Status ControlPoint::SetType(PointType newType) { if (type != Fixed && type != Free && type != Constrained) { QString msg = "Invalid Point Enumeration, [" + QString(type) + "], for " "Control Point [" + GetId() + "]"; throw IException(IException::Programmer, msg, _FILEINFO_); } if (editLock) { return PointLocked; } if (parentNetwork) { parentNetwork->emitPointModified(this, ControlPoint::TypeModified, type, newType); } PointModified(); type = newType; return Success; } /** * This updates the source of the radius of the apriori surface point. * * @see RadiusSource::Source * * @param source Where the radius came from */ ControlPoint::Status ControlPoint::SetAprioriRadiusSource( RadiusSource::Source source) { if (editLock) { return PointLocked; } PointModified(); aprioriRadiusSource = source; return Success; } /** * This updates the filename of the DEM that the apriori radius came from. It * doesn't really make sense to call this unless the RadiusSource is DEM. * * @see RadiusSource::Source * * @param source Where the radius came from */ ControlPoint::Status ControlPoint::SetAprioriRadiusSourceFile( QString sourceFile) { if (editLock) { return PointLocked; } PointModified(); aprioriRadiusSourceFile = sourceFile; return Success; } /** * This updates the apriori surface point. * * @see SetAprioriRadiusSource * @see SetAprioriRadiusSourceFile * @see SetAprioriPointSource * @see SetAprioriPointSourceFile * @see aprioriSurfacePoint * * @param aprioriSP The apriori surface point to remember * @todo This method needs to be revisited. It will set the constraint * status based on the sigmas and override the existing status. */ ControlPoint::Status ControlPoint::SetAprioriSurfacePoint( SurfacePoint aprioriSP) { SurfacePoint::CoordinateType coordType = SurfacePoint::Latitudinal; if (parentNetwork) { coordType = parentNetwork->GetCoordType(); } if (editLock) { return PointLocked; } // ***TBD*** Does it make sense to try to do a generic check here? The // data types are different (angles vs distance) so for now do a switch. switch (coordType) { case SurfacePoint::Latitudinal: if (aprioriSP.GetLatSigma().isValid()) constraintStatus.set(Coord1Constrained); if (aprioriSP.GetLonSigma().isValid()) constraintStatus.set(Coord2Constrained); if (aprioriSP.GetLocalRadiusSigma().isValid()) constraintStatus.set(Coord3Constrained); break; case SurfacePoint::Rectangular: if (aprioriSP.GetXSigma().isValid()) constraintStatus.set(Coord1Constrained); if (aprioriSP.GetYSigma().isValid()) constraintStatus.set(Coord2Constrained); if (aprioriSP.GetZSigma().isValid()) constraintStatus.set(Coord3Constrained); } PointModified(); aprioriSurfacePoint = aprioriSP; return Success; } /** * This updates the source of the surface point * * @see SurfacePointSource::Source * * @param source Where the surface point came from */ ControlPoint::Status ControlPoint::SetAprioriSurfacePointSource( SurfacePointSource::Source source) { if (editLock) { return PointLocked; } PointModified(); aprioriSurfacePointSource = source; return Success; } /** * This updates the filename of where the apriori surface point came from. * * @see RadiusSource::Source * * @param sourceFile Where the surface point came from */ ControlPoint::Status ControlPoint::SetAprioriSurfacePointSourceFile( QString sourceFile) { if (editLock) { return PointLocked; } PointModified(); aprioriSurfacePointSourceFile = sourceFile; return Success; } /** * Computes a priori lat/lon/radius point coordinates by determining the average lat/lon/radius of * all measures. Note that this does not change ignored, fixed or constrained points. * * Also, it does not use unmeasured or ignored measures when computing lat/lon/radius. * * (KLE) Note this not a rigorous triangulation considering outliers. A better way would be to... * a) use e.g. a closest approach algorithm to find intersection of all rays, regardless * of whether the intersection lies on the surface in question, then; * b) perform a rigorous triangulation with some sort outlier detection approach, a robust * estimation technique (perhaps RANSAC) * * @internal * @history 2008-06-18 Tracie Sucharski/Jeannie Walldren, * Changed error messages for * Held/Ground points. * @history 2009-10-13 Jeannie Walldren - Added detail to * error message. * @history 2010-11-29 Tracie Sucharski - Remove call to ControlMeasure:: * SetMeasuredEphemerisTime, the values were * never used. so these methods were removed * from ControlMeasure and the call was removed * here. * @history 2010-12-02 Debbie A. Cook - Added units to SetRectangular * calls since default is meters and units * are km. * @history 2011-03-17 Debbie A. Cook - Added initialization of * adjustedSurfacePoint to aprioriSurfacePoint * and set test for empty covariance matrix * to use 0. instead of nulls. * @history 2011-03-24 Debbie A. Cook - Removed IsMeasured check since it * was really checking for Candidate measures. * @history 2011-07-12 Debbie A. Cook - Removed editLock test. Users agreed * editLock was only for fixed and constrained * points, which are already left unchanged by * ComputeApriori. If a free point is editLocked * the editLock will be ignored by this method. * @history 2017-04-25 Debbie A. Cook - change constraint status calls * to use generic coordinate names (Coord1, Coord2, * and Coord3). * @history 2019-03-10 Ken Edmundson - Fixed bug where focal plane measured x,y coordinates were * not set if the cam->SetImage call failed. Setting the measured * focal plane coordinates should not depend upon the success of the * SetImage call (References #2591). Improved error messages. * Cleaned up code. Added comments above to suggest a more rigorous * approach to computing a priori point coordinates. * * @return Status Success or PointLocked */ ControlPoint::Status ControlPoint::ComputeApriori() { // TODO (KLE): where should call this go? Also, what's the point? The method has no description. PointModified(); // if point is fixed or constrained, ensure valid a priori point coordinates exist if ( (IsFixed() || IsConstrained()) && !aprioriSurfacePoint.Valid() ) { QString msg = "In method ControlPoint::ComputeApriori(). ControlPoint [" + GetId() + "] is "; msg += "fixed or constrained and requires a priori coordinates"; throw IException(IException::User, msg, _FILEINFO_); } double xB = 0.0; // body-fixed x double yB = 0.0; // body-fixed y double zB = 0.0; // body-fixed z double r2B = 0.0; // radius squared in body-fixed int goodMeasures = 0; double pB[3]; bool computeApriori = false; // loop over measures to ... // 1) set focal plane x,y coordinates for all unignored measures; // 2) sum latitude, longitude, and radius coordinates in preparation for computing a priori // coordinates by averaging. for (int i = 0; i < cubeSerials->size(); i++) { ControlMeasure *m = GetMeasure(i); if (m->IsIgnored()) { continue; } Camera *cam = m->Camera(); if (cam == NULL) { QString cubeSN = m->GetCubeSerialNumber(); QString msg = "in method ControlPoint::ComputeApriori(). Camera has not been set in "; msg += "measure for cube serial number [" + cubeSN + "], Control Point id "; msg += "[" + GetId() + "]. Camera must be set prior to calculating a priori coordinates"; throw IException(IException::Programmer, msg, _FILEINFO_); } bool setImageSuccess = cam->SetImage(m->GetSample(), m->GetLine()); m->SetFocalPlaneMeasured(cam->DistortionMap()->UndistortedFocalPlaneX(), cam->DistortionMap()->UndistortedFocalPlaneY()); // TODO: Seems like we should be able to skip this computation if point is fixed or // constrained in any coordinate. Currently we are always summing coordinates here. We could // save time by not doing this for fixed or constrained points. if (!IsFixed() && !IsConstrained() && !id.contains("Lidar")) { computeApriori = true; if (setImageSuccess) { goodMeasures++; cam->Coordinate(pB); xB += pB[0]; yB += pB[1]; zB += pB[2]; r2B += pB[0]*pB[0] + pB[1]*pB[1] + pB[2]*pB[2]; } } } // if we don't have to compute the a priori coordinates, i.e. // if point is Fixed or Constrained in any number of coordinates, or is a lidar point, // then just initialize adjustedSurfacePoint to a priori coordinates (set in e.g. qnet or // cneteditor) and exit if (!computeApriori) { adjustedSurfacePoint = aprioriSurfacePoint; return Success; } // if point is Free, we continue to compute a priori coordinates // if no good measures, we're done // TODO: is the message true/meaningful? if (goodMeasures == 0) { QString msg = "in method ControlPoint::ComputeApriori(). ControlPoint [" + GetId() + "] has "; msg += "no measures which project to the body"; throw IException(IException::User, msg, _FILEINFO_); // adjustedSurfacePoint = aprioriSurfacePoint; // return Success; } // Compute the averages if all coordinates are free // TODO: confirm if this "if" statement is necessary if (GetType() == Free || NumberOfConstrainedCoordinates() == 0) { double avgX = xB / goodMeasures; double avgY = yB / goodMeasures; double avgZ = zB / goodMeasures; double avgR2 = r2B / goodMeasures; double scale = sqrt(avgR2/(avgX*avgX+avgY*avgY+avgZ*avgZ)); aprioriSurfacePoint.SetRectangular( Displacement((avgX*scale), Displacement::Kilometers), Displacement((avgY*scale), Displacement::Kilometers), Displacement((avgZ*scale), Displacement::Kilometers)); } adjustedSurfacePoint = aprioriSurfacePoint; SetAprioriSurfacePointSource(SurfacePointSource::AverageOfMeasures); SetAprioriRadiusSource(RadiusSource::AverageOfMeasures); return Success; } /** * This method computes the BundleAdjust residuals for a point. * *** Warning: Only BundleAdjust and its applications should be * using this method. * * @history 2008-07-17 Tracie Sucharski, Added ptid and measure serial * number to the unable to map to surface error. * @history 2009-12-06 Tracie Sucharski, Renamed from ComputeErrors * @history 2010-08-05 Tracie Sucharski, Changed lat/lon/radius to x/y/z * @history 2010-12-10 Debbie A. Cook, Revised error calculation for radar * because it was always reporting line errors=0. * @history 2011-03-17 Debbie A. Cook, Fixed typo in radar call to get * longitude * @history 2011-03-24 Debbie A. Cook, Removed IsMeasured check since it * was really checking for Candidate measures. * @history 2011-07-01 Debbie A. Cook, Removed editLock check to allow * BundleAdjust to compute residuals for * editLocked points * @history 2012-01-18 Debbie A. Cook, Revised to call * ComputeResidualsMillimeters() to avoid * duplication of code * @history 2018-06-13 Debbie A. Cook, Ken Edmundson, Removed method ComputeResidualsMillimeters() * and the call to it that was in this method. Added computation of * focal plane computedx and computedy here. */ ControlPoint::Status ControlPoint::ComputeResiduals() { if (IsIgnored()) { return Failure; } PointModified(); double cuSamp, cuLine; double muSamp, muLine; double cudx = 0.0; double cudy = 0.0; // Loop for each measure to compute the error QList<QString> keys = measures->keys(); for (int j = 0; j < keys.size(); j++) { ControlMeasure *m = (*measures)[keys[j]]; if (m->IsIgnored()) { continue; } // The following lines actually check for Candidate measures // Commented out on 2011-03-24 by DAC // if (!m->IsMeasured()) { // continue; Camera *cam = m->Camera(); CameraFocalPlaneMap *fpmap = cam->FocalPlaneMap(); // Map the lat/lon/radius of the control point through the Spice of the // measurement sample/line to get the computed undistorted focal plane // coordinates (mm if not radar). This works for radar too because in // the undistorted focal plane, y has not been set to 0 (set to 0 when // going to distorted focal plane or ground range in this case), so we // can hold the Spice to calculate residuals in undistorted focal plane // coordinates. if (cam->GetCameraType() != 0) { // no need to call setimage for framing camera cam->SetImage(m->GetSample(), m->GetLine()); } // cam->GroundMap()->GetXY(GetAdjustedSurfacePoint(), &cudx, &cudy); cam->GroundMap()->GetXY(adjustedSurfacePoint, &cudx, &cudy); m->SetFocalPlaneComputed(cudx, cudy); // TODO:TESTING // cam->DistortionMap()->SetUndistortedFocalPlane(cudx,cudy); // double distortedx = cam->DistortionMap()->FocalPlaneX(); // double distortedy = cam->DistortionMap()->FocalPlaneY(); // fpmap->SetFocalPlane(distortedx, distortedy); // double distortedsample = fpmap->DetectorSample(); // double distortedline = fpmap->DetectorLine(); // TODO:TESTING if (cam->GetCameraType() != Isis::Camera::Radar) { // Now things get tricky. We want to produce errors in pixels not mm // but some of the camera maps could fail. One that won't is the // FocalPlaneMap which takes x/y to detector s/l. We will bypass the // distortion map and have residuals in undistorted pixels. if (!fpmap->SetFocalPlane(cudx, cudy)) { QString msg = "Sanity check #1 for ControlPoint [" + GetId() + "], ControlMeasure [" + m->GetCubeSerialNumber() + "]"; throw IException(IException::Programmer, msg, _FILEINFO_); // This error shouldn't happen but check anyways } cuSamp = fpmap->DetectorSample(); cuLine = fpmap->DetectorLine(); } else { // For radar line is calculated from time in the camera. Use the // closest line to scale the focal plane y (doppler shift) to image line // for computing the line residual. Get a local ratio // measureLine = adjacentLine // ------------ -------------- in both cases, doppler shift // dopplerMLine dopplerAdjLine is calculated using SPICE // at the time of the measurement // // 1. Get the surface point mapped to by an adjacent pixel above (if // doppler is < 0) or below (if doppler is > 0) the measured pixel // 2. Set image to the measured sample/line to load the SPICE for the // time of the measurement. // 3. Map the surface point from the adjacent pixel through the SPICE // into the image plane to get a scale for mapping from doppler // shift to line. Apply the scale to get the line residual double sample = m->GetSample(); double computedY = m->GetFocalPlaneComputedY(); double computedX = m->GetFocalPlaneComputedX(); double adjLine; // Step 1. What happens if measured line is 1??? TODO if (computedY < 0) { adjLine = m->GetLine() - 1.; } else { adjLine = m->GetLine() + 1.; } cam->SetImage(sample, adjLine); SurfacePoint sp = cam->GetSurfacePoint(); // Step 2. cam->SetImage(sample, m->GetLine()); double focalplaneX; double scalingY; // Step 3. cam->GroundMap()->GetXY(sp, &focalplaneX, &scalingY); double deltaLine; if (computedY < 0) { deltaLine = -computedY/scalingY; } else { deltaLine = computedY/scalingY; } // Now map through the camera steps to take X from slant range to ground // range to pixels. Y just tracks through as 0. if (cam->DistortionMap()->SetUndistortedFocalPlane(computedX, computedY)){ double focalPlaneX = cam->DistortionMap()->FocalPlaneX(); double focalPlaneY = cam->DistortionMap()->FocalPlaneY(); fpmap->SetFocalPlane(focalPlaneX,focalPlaneY); } cuSamp = fpmap->DetectorSample(); cuLine = m->GetLine() + deltaLine; } if (cam->GetCameraType() != Isis::Camera::Radar) { // Again we will bypass the distortion map and have residuals in undistorted pixels. if (!fpmap->SetFocalPlane(m->GetFocalPlaneMeasuredX(), m->GetFocalPlaneMeasuredY())) { QString msg = "Sanity check #2 for ControlPoint [" + GetId() + "], ControlMeasure [" + m->GetCubeSerialNumber() + "]"; throw IException(IException::Programmer, msg, _FILEINFO_); // This error shouldn't happen but check anyways } muSamp = fpmap->DetectorSample(); muLine = fpmap->DetectorLine(); } else { muSamp = m->GetSample(); muLine = m->GetLine(); } // The units are in detector sample/lines. We will apply the instrument // summing mode to get close to real pixels. Note however we are in // undistorted pixels except for radar instruments. double sampResidual = muSamp - cuSamp; double lineResidual = muLine - cuLine; // TODO: TESTING // double x = cam->DistortionMap()->FocalPlaneX(); // double y = cam->DistortionMap()->FocalPlaneY(); // m->SetFocalPlaneMeasured(x, y); // double mdissamp = fpmap->DetectorSample(); // double mdisline = fpmap->DetectorLine(); // double distortedSampleResidual = fpmap->DetectorSample() - distortedsample; // double distortedLineResidual = fpmap->DetectorLine() - distortedline; // TODO: TESTING m->SetResidual(sampResidual, lineResidual); } return Success; } QString ControlPoint::GetChooserName() const { if (chooserName != "") { return chooserName; } else { return FileName(Application::Name()).name(); } } //! Returns true if the choosername is not empty. bool ControlPoint::HasChooserName() const { return !chooserName.isEmpty(); } //! Returns true if the datetime is not empty. bool ControlPoint::HasDateTime() const { return !dateTime.isEmpty(); } QString ControlPoint::GetDateTime() const { if (dateTime != "") { return dateTime; } else { return Application::DateTime(); } } bool ControlPoint::IsEditLocked() const { return editLock; } bool ControlPoint::IsRejected() const { return jigsawRejected; } SurfacePoint ControlPoint::GetAdjustedSurfacePoint() const { return adjustedSurfacePoint; } /** * Returns the adjusted surface point if it exists, otherwise returns * the a priori surface point. */ SurfacePoint ControlPoint::GetBestSurfacePoint() const { if (adjustedSurfacePoint.Valid()) { return adjustedSurfacePoint; } else { return aprioriSurfacePoint; } } /** * Return the Id of the control point * * @return Control Point Id */ QString ControlPoint::GetId() const { return id; } bool ControlPoint::IsIgnored() const { return ignore; } bool ControlPoint::IsValid() const { return !invalid; } bool ControlPoint::IsInvalid() const { return invalid; } /** * Obtain a string representation of a given PointType * * @param type PointType to convert to a string * * @returns A string representation of type */ QString ControlPoint::PointTypeToString(PointType pointType) { QString str; switch (pointType) { case Fixed: str = "Fixed"; break; case Constrained: str = "Constrained"; break; case Free: str = "Free"; break; } return str; } /** * Obtain a PointType given a string representation of it. * * @param pointTypeString for the requested PointType * * @returns the PointType for the given string */ ControlPoint::PointType ControlPoint::StringToPointType( QString pointTypeString) { // On failure assume Free ControlPoint::PointType type = ControlPoint::Free; QString errMsg = "There is no PointType that has a string representation" " of \""; errMsg += pointTypeString; errMsg += "\"."; if (pointTypeString == "Fixed") { type = ControlPoint::Fixed; } else if (pointTypeString == "Constrained") { type = ControlPoint::Constrained; } else if (pointTypeString == "Free") { type = ControlPoint::Free; } else { throw IException(IException::Programmer, errMsg, _FILEINFO_); } return type; } /** * Obtain a string representation of the PointType * * @return A string representation of the PointType */ QString ControlPoint::GetPointTypeString() const { return PointTypeToString(GetType()); } /** * @returns this point't type * */ ControlPoint::PointType ControlPoint::GetType() const { return type; } /** * Obtain a string representation of a given RadiusSource * * @param source RadiusSource to convert to string * * @returns A string representation of RadiusSource */ QString ControlPoint::RadiusSourceToString(RadiusSource::Source source) { QString str; switch (source) { case RadiusSource::None: str = "None"; break; case RadiusSource::User: str = "User"; break; case RadiusSource::AverageOfMeasures: str = "AverageOfMeasures"; break; case RadiusSource::Ellipsoid: str = "Ellipsoid"; break; case RadiusSource::DEM: str = "DEM"; break; case RadiusSource::BundleSolution: str = "BundleSolution"; break; } return str; } /** * Obtain a RadiusSource::Source from a string * * @param str string to get a RadiusSource::Source from * * @returns The RadiusSource::Source matching the given string */ ControlPoint::RadiusSource::Source ControlPoint::StringToRadiusSource( QString str) { str = str.toLower(); RadiusSource::Source source = RadiusSource::None; if (str == "user") { source = RadiusSource::User; } else if (str == "averageofmeasures") { source = RadiusSource::AverageOfMeasures; } else if (str == "ellipsoid") { source = RadiusSource::Ellipsoid; } else if (str == "dem") { source = RadiusSource::DEM; } else if (str == "bundlesolution") { source = RadiusSource::BundleSolution; } return source; } /** * Obtain a string representation of the RadiusSource * * @return A string representation of the RadiusSource */ QString ControlPoint::GetRadiusSourceString() const { return RadiusSourceToString(aprioriRadiusSource); } /** * Obtain a string representation of a given SurfacePointSource * * @param souce SurfacePointSource to get a string representation of * * @returns A string representation of SurfacePointSource */ QString ControlPoint::SurfacePointSourceToString( SurfacePointSource::Source source) { QString str; switch (source) { case SurfacePointSource::None: str = "None"; break; case SurfacePointSource::User: str = "User"; break; case SurfacePointSource::AverageOfMeasures: str = "AverageOfMeasures"; break; case SurfacePointSource::Reference: str = "Reference"; break; case SurfacePointSource::Basemap: str = "Basemap"; break; case SurfacePointSource::BundleSolution: str = "BundleSolution"; break; } return str; } /** * Obtain a SurfacePoint::Source from a string * * @param str string to get a SurfacePoint::Source from * * @returns The SurfacePint::Source matching the given string */ ControlPoint::SurfacePointSource::Source ControlPoint::StringToSurfacePointSource( QString str) { str = str.toLower(); SurfacePointSource::Source source = SurfacePointSource::None; if (str == "user") { source = SurfacePointSource::User; } else if (str == "averageofmeasures") { source = SurfacePointSource::AverageOfMeasures; } else if (str == "reference") { source = SurfacePointSource::Reference; } else if (str == "basemap") { source = SurfacePointSource::Basemap; } else if (str == "bundlesolution") { source = SurfacePointSource::BundleSolution; } return source; } /** * Obtain a string representation of the SurfacePointSource * * @return A string representation of the SurfacePointSource */ QString ControlPoint::GetSurfacePointSourceString() const { return SurfacePointSourceToString(aprioriSurfacePointSource); } bool ControlPoint::IsFixed() const { return (type == Fixed); } SurfacePoint ControlPoint::GetAprioriSurfacePoint() const { return aprioriSurfacePoint; } ControlPoint::RadiusSource::Source ControlPoint::GetAprioriRadiusSource() const { return aprioriRadiusSource; } bool ControlPoint::HasAprioriCoordinates() { if (aprioriSurfacePoint.GetX().isValid() && aprioriSurfacePoint.GetY().isValid() && aprioriSurfacePoint.GetZ().isValid()) { return true; } return false; // return aprioriSurfacePoint.Valid(); ??? } bool ControlPoint::IsConstrained() { return constraintStatus.any(); } bool ControlPoint::IsCoord1Constrained() { return constraintStatus[Coord1Constrained]; } bool ControlPoint::IsCoord2Constrained() { return constraintStatus[Coord2Constrained]; } bool ControlPoint::IsCoord3Constrained() { return constraintStatus[Coord3Constrained]; } int ControlPoint::NumberOfConstrainedCoordinates() { return constraintStatus.count(); } /** * Checks to see if the radius source file has been set. * * @return bool True if the radius source file has been set. */ bool ControlPoint::HasAprioriRadiusSourceFile() const { return !( aprioriRadiusSourceFile.isEmpty() || aprioriRadiusSourceFile.isNull() ); } QString ControlPoint::GetAprioriRadiusSourceFile() const { return aprioriRadiusSourceFile; } ControlPoint::SurfacePointSource::Source ControlPoint::GetAprioriSurfacePointSource() const { return aprioriSurfacePointSource; } /** * Checks to see if the surface point source file has been set. * * @return bool True if the surface point source file has been set. */ bool ControlPoint::HasAprioriSurfacePointSourceFile() const { return !( aprioriSurfacePointSourceFile.isEmpty() || aprioriSurfacePointSourceFile.isNull() ); } QString ControlPoint::GetAprioriSurfacePointSourceFile() const { return aprioriSurfacePointSourceFile; } int ControlPoint::GetNumMeasures() const { return measures->size(); } /** * * @return Number of valid control measures */ int ControlPoint::GetNumValidMeasures() const { int size = 0; QList<QString> keys = measures->keys(); for (int cm = 0; cm < keys.size(); cm++) { if (!(*measures)[keys[cm]]->IsIgnored()) { size++; } } return size; } /** * Returns the number of locked control measures * * @return Number of locked control measures */ int ControlPoint::GetNumLockedMeasures() const { int size = 0; QList<QString> keys = measures->keys(); for (int cm = 0; cm < keys.size(); cm++) { if ((*measures)[keys[cm]]->IsEditLocked()) { size++; } } return size; } /** * Return true if given serial number exists in point * * @param serialNumber The serial number * @return True if point contains serial number, false if not */ bool ControlPoint::HasSerialNumber(QString serialNumber) const { return cubeSerials->contains(serialNumber); } /** * @returns true Returns true if SetRefMeasure has ever been set on this * point. */ bool ControlPoint::IsReferenceExplicit() const { return referenceExplicitlySet; } /** * @returns The cube serial number of the reference measure */ QString ControlPoint::GetReferenceSN() const { if (!HasRefMeasure()) { QString msg = "There is no reference measure set in the ControlPoint [" + GetId() + "]"; throw IException(IException::Programmer, msg, _FILEINFO_); } return referenceMeasure->GetCubeSerialNumber(); } /** * @param cm The control measure to find the index of * @param throws Throws an exception on failure instead of returning -1. * Be aware that by default this is true! * * @returns The index of the passed in measure, or -1 on failure if throws * is false. */ int ControlPoint::IndexOf(ControlMeasure *cm, bool throws) const { ASSERT(cm); return IndexOf(cm->GetCubeSerialNumber(), throws); } /** * @param sn The serial number of the control measure to find the index of * @param throws Throws an exception on failure instead of returning -1. * Be aware that by default this is true! * * @returns The index of the measure with serial number matching sn, * or -1 on failure if throws is false. */ int ControlPoint::IndexOf(QString sn, bool throws) const { int index = cubeSerials->indexOf(sn); if (throws && index == -1) { QString msg = "ControlMeasure [" + sn + "] does not exist in point [" + id + "]"; throw IException(IException::Programmer, msg, _FILEINFO_); } return index; } /** * @param throws Throws an exception on failure instead of returning -1. * Be aware that by default this is true! * * @returns The index of the reference measure, or -1 if no measures exist * in the point (A point with at least one measure ALWAYS has a reference * measure. */ int ControlPoint::IndexOfRefMeasure() const { if (!HasRefMeasure()) { QString msg = "There is no reference measure for point [" + id + "]." " This also means of course that the point is empty!"; throw IException(IException::Programmer, msg, _FILEINFO_); } int index = cubeSerials->indexOf(referenceMeasure->GetCubeSerialNumber()); ASSERT(index != -1) return index; } /** * This function will call a given method on every control measure that * this point has. * * @param statFunc The function to use for data collection * * @returns The gathered statistic * * @history 2011-03-08 Debbie A. Cook - Changed to get statistics for all * point types and not just Candidate. */ Statistics ControlPoint::GetStatistic( double(ControlMeasure::*statFunc)() const) const { Statistics stats; foreach(ControlMeasure * cm, *measures) { if (!cm->IsIgnored()) { stats.AddData((cm->*statFunc)()); } } return stats; } Statistics ControlPoint::GetStatistic(long dataType) const { Statistics stats; foreach(ControlMeasure * cm, *measures) { if (!cm->IsIgnored()) { stats.AddData(cm->GetLogData(dataType).GetNumericalValue()); } } return stats; } /** * @param excludeIgnored Ignored measures are excluded if this is true. It * is false by default. * * @returns A list of this points measures */ QList< ControlMeasure * > ControlPoint::getMeasures( bool excludeIgnored) const { QList< ControlMeasure * > orderedMeasures; for (int i = 0; i < cubeSerials->size(); i++) { ControlMeasure *measure = measures->value((*cubeSerials)[i]); if (!excludeIgnored || !measure->IsIgnored()) { orderedMeasures.append(measures->value((*cubeSerials)[i])); } } return orderedMeasures; } /** * @returns A list of cube serial numbers */ QList< QString > ControlPoint::getCubeSerialNumbers() const { return *cubeSerials; } /** * Same as GetMeasure (provided for convenience) * * @param serialNumber Cube serial number of desired control measure * * @returns const version of the measure which has the provided serial number */ const ControlMeasure *ControlPoint::operator[](QString serialNumber) const { return GetMeasure(serialNumber); } /** * Same as GetMeasure (provided for convenience) * * @param serialNumber Cube serial number of desired control measure * * @returns The measure which has the provided serial number */ ControlMeasure *ControlPoint::operator[](QString serialNumber) { return GetMeasure(serialNumber); } /** * Same as GetMeasure (provided for convenience) * * @param index If there are n measures, the measure returned will be the * ith measure added to the point * * @returns const version of the measure which has the provided serial number */ const ControlMeasure *ControlPoint::operator[](int index) const { return GetMeasure(index); } /** * Same as GetMeasure (provided for convenience) * * @param index If there are n measures, the measure returned will be the * ith measure added to the point * * @returns The measure which has the provided serial number */ ControlMeasure *ControlPoint::operator[](int index) { return GetMeasure(index); } /** * Compare two Control Points for inequality * * @param other The other point to compare this one to * * @returns true if the two points are not equal, and false otherwise */ bool ControlPoint::operator!=(const ControlPoint &other) const { return !(*this == other); } /** * Compare two Control Points for equality * * @param other The other point to compare to * * @returns true if the two points are equal, and false otherwise * */ bool ControlPoint::operator==(const ControlPoint &other) const { return other.GetNumMeasures() == GetNumMeasures() && other.id == id && other.type == type && other.chooserName == chooserName && other.editLock == editLock && other.ignore == ignore && other.aprioriSurfacePointSource == aprioriSurfacePointSource && other.aprioriSurfacePointSourceFile == aprioriSurfacePointSourceFile && other.aprioriRadiusSource == aprioriRadiusSource && other.aprioriRadiusSourceFile == aprioriRadiusSourceFile && other.aprioriSurfacePoint == aprioriSurfacePoint && other.adjustedSurfacePoint == adjustedSurfacePoint && other.invalid == invalid && other.measures == measures && other.dateTime == dateTime && other.jigsawRejected == jigsawRejected && other.constraintStatus == constraintStatus && other.referenceExplicitlySet == referenceExplicitlySet && other.numberOfRejectedMeasures == numberOfRejectedMeasures && other.cubeSerials == cubeSerials && other.referenceMeasure == referenceMeasure; } /** * * @param pPoint * * @return ControlPoint& * * @internal * @history 2011-09-13 Eric Hyer,Tracie Sucharski - Changed input parameter * to const &. Re-wrote using Delete and AddMeasure * methods, so that the ControlGraphNode is updated * correctly. * @history 2011-09-30 Tracie Sucharski - Fixed some memory leaks and * deleted some calls that were already handled in * AddMeasure. * @history 2011-10-03 Tracie Sucharski - Unlock measures before Deleting */ const ControlPoint &ControlPoint::operator=(const ControlPoint &other) { if (this != &other) { bool oldLock = editLock; editLock = false; for (int i = cubeSerials->size() - 1; i >= 0; i--) { (*measures)[cubeSerials->at(i)]->SetEditLock(false); Delete(cubeSerials->at(i)); } //measures->clear(); = new QHash< QString, ControlMeasure * >; QHashIterator< QString, ControlMeasure * > i(*other.measures); while (i.hasNext()) { i.next(); ControlMeasure *newMeasure = new ControlMeasure; *newMeasure = *i.value(); AddMeasure(newMeasure); if (other.referenceMeasure == i.value()) { SetRefMeasure(newMeasure); } } invalid = other.invalid; referenceExplicitlySet = other.referenceExplicitlySet; numberOfRejectedMeasures = other.numberOfRejectedMeasures; constraintStatus = other.constraintStatus; SetId(other.id); SetChooserName(other.chooserName); SetDateTime(other.dateTime); SetType(other.type); SetRejected(other.jigsawRejected); SetIgnored(other.ignore); SetAprioriSurfacePointSource(other.aprioriSurfacePointSource); SetAprioriSurfacePointSourceFile(other.aprioriSurfacePointSourceFile); SetAprioriRadiusSource(other.aprioriRadiusSource); SetAprioriRadiusSourceFile(other.aprioriRadiusSourceFile); SetAprioriSurfacePoint(other.aprioriSurfacePoint); SetAdjustedSurfacePoint(other.adjustedSurfacePoint); // Set edit lock last so the it doesn't interfere with copying the other fields over. editLock = oldLock; SetEditLock(other.editLock); } return *this; } void ControlPoint::PointModified() { dateTime = ""; } //! Initialize the number of rejected measures to 0 void ControlPoint::ZeroNumberOfRejectedMeasures() { numberOfRejectedMeasures = 0; } /** * Set (update) the number of rejected measures for the control point * * @param numRejected The number of rejected measures * */ void ControlPoint::SetNumberOfRejectedMeasures(int numRejected) { numberOfRejectedMeasures = numRejected; } /** * Get the number of rejected measures on the control point * * @return The number of rejected measures on this control point * */ int ControlPoint::GetNumberOfRejectedMeasures() const { return numberOfRejectedMeasures; } /** * Get rms of sample residuals * * @return The rms of sample residuals * */ double ControlPoint::GetSampleResidualRms() const { int nmeasures = measures->size(); if ( nmeasures <= 0 ) { return 0.0; } Statistics stats; for( int i = 0; i < nmeasures; i++) { const ControlMeasure* m = GetMeasure(i); if ( !m ) { continue; } if ( !m->IsIgnored() || m->IsRejected() ) { continue; } stats.AddData(m->GetSampleResidual()); } return stats.Rms(); } /** * Get rms of line residuals * * @return The rms of line residuals * */ double ControlPoint::GetLineResidualRms() const { int nmeasures = measures->size(); if ( nmeasures <= 0 ) { return 0.0; } Statistics stats; for( int i = 0; i < nmeasures; i++) { const ControlMeasure* m = GetMeasure(i); if ( !m ) { continue; } if ( !m->IsIgnored() || m->IsRejected() ) { continue; } stats.AddData(m->GetLineResidual()); } return stats.Rms(); } /** * Get rms of residuals * * @return The rms of residuals * */ double ControlPoint::GetResidualRms() const { int nmeasures = measures->size(); if ( nmeasures <= 0 ) { return 0.0; } Statistics stats; for( int i = 0; i < nmeasures; i++) { const ControlMeasure* m = GetMeasure(i); if ( !m ) { continue; } if ( m->IsIgnored() || m->IsRejected() ) { continue; } stats.AddData(m->GetSampleResidual()); stats.AddData(m->GetLineResidual()); } return stats.Rms(); } /** * Set jigsaw rejected flag for all measures to false * and set the jigsaw rejected flag for the point itself to false * */ void ControlPoint::ClearJigsawRejected() { int nmeasures = measures->size(); if ( nmeasures <= 0 ) { return; } for( int i = 0; i < nmeasures; i++) { ControlMeasure* m = GetMeasure(i); if ( !m ) { continue; } m->SetRejected(false); } SetRejected(false); } }