7 #include "PlusConfigure.h" 8 #include "igsioTrackedFrame.h" 9 #include "vtkObjectFactory.h" 10 #include "vtkDoubleArray.h" 13 #include "vtkPiecewiseFunction.h" 17 #include "vtkIGSIOTrackedFrameList.h" 31 const double MINIMUM_TRACKER_SIGNAL_PEAK_TO_PEAK_MM = 8.0;
33 const double MINIMUM_VIDEO_SIGNAL_PEAK_TO_PEAK_PIXEL = 30.0;
35 const double TIMESTAMP_EPSILON_SEC = 0.0001;
37 const double MINIMUM_SAMPLING_RESOLUTION_SEC = 0.00001;
38 const double DEFAULT_SAMPLING_RESOLUTION_SEC = 0.001;
39 const double DEFAULT_MAX_MOVING_LAG_SEC = 0.5;
46 SIGNAL_METRIC_TYPE_COUNT
48 const double SIGNAL_ALIGNMENT_METRIC_THRESHOLD[SIGNAL_METRIC_TYPE_COUNT] =
65 vtkPlusTemporalCalibrationAlgo::vtkPlusTemporalCalibrationAlgo()
66 : TrackerLagUpToDate(false)
68 , SaveIntermediateImages(false)
69 , IntermediateFilesOutputDirectory(
vtkPlusConfig::GetInstance()->GetOutputDirectory())
70 , SamplingResolutionSec(DEFAULT_SAMPLING_RESOLUTION_SEC)
71 , BestCorrelationValue(0.0)
72 , BestCorrelationLagIndex(-1)
73 , BestCorrelationTimeOffset(0.0)
75 , CalibrationError(0.0)
76 , MaxCalibrationError(0.0)
77 , MaxMovingLagSec(DEFAULT_MAX_MOVING_LAG_SEC)
78 , BestCorrelationNormalizationFactor(0.0)
79 , FixedSignalValuesNormalizationFactor(0.0)
81 this->FixedSignal.frameList = NULL;
82 this->MovingSignal.frameList = NULL;
83 this->LineSegmentationClipRectangleOrigin[0] = 0;
84 this->LineSegmentationClipRectangleOrigin[1] = 0;
85 this->LineSegmentationClipRectangleSize[0] = 0;
86 this->LineSegmentationClipRectangleSize[1] = 0;
91 vtkPlusTemporalCalibrationAlgo::~vtkPlusTemporalCalibrationAlgo()
169 if (samplingResolutionSec < MINIMUM_SAMPLING_RESOLUTION_SEC)
171 LOG_ERROR(
"Specified resampling resolution (" << samplingResolutionSec <<
" seconds) is too small. Sampling resolution must be greater than: " << MINIMUM_SAMPLING_RESOLUTION_SEC <<
" seconds");
194 LOG_ERROR(
"You must first call the \"Update()\" to compute the tracker lag.");
206 LOG_ERROR(
"You must first call the \"Update()\" to compute the best correlation metric.");
218 LOG_ERROR(
"You must first call the \"Update()\" to compute the calibration error.");
230 LOG_ERROR(
"You must first call the \"Update()\" to compute the calibration error.");
241 if (unCalibratedMovingPositionSignal->GetNumberOfColumns() != 2)
243 LOG_ERROR(
"Error in constructing the vtk tables that are to hold moving signal. Table has " <<
244 unCalibratedMovingPositionSignal->GetNumberOfColumns() <<
" columns, but should have two columns");
247 unCalibratedMovingPositionSignal->GetColumn(0)->SetName(
"Time [s]");
248 unCalibratedMovingPositionSignal->GetColumn(1)->SetName(
"Uncalibrated Moving Signal");
256 if (calibratedMovingPositionSignal->GetNumberOfColumns() != 2)
258 LOG_ERROR(
"Error in constructing the vtk tables that are to hold moving signal. Table has " <<
259 calibratedMovingPositionSignal->GetNumberOfColumns() <<
" columns, but should have two columns");
262 calibratedMovingPositionSignal->GetColumn(0)->SetName(
"Time [s]");
263 calibratedMovingPositionSignal->GetColumn(1)->SetName(
"Calibrated Moving Signal");
271 if (fixedPositionSignal->GetNumberOfColumns() != 2)
273 LOG_ERROR(
"Error in constructing the vtk tables that are to hold fixed signal. Table has " <<
274 fixedPositionSignal->GetNumberOfColumns() <<
" columns, but should have two columns");
277 fixedPositionSignal->GetColumn(0)->SetName(
"Time [s]");
278 fixedPositionSignal->GetColumn(1)->SetName(
"Fixed Signal");
286 if (correlationSignal->GetNumberOfColumns() != 2)
288 LOG_ERROR(
"Error in constructing the vtk tables that are to hold correlated signal. Table has " <<
289 correlationSignal->GetNumberOfColumns() <<
" columns, but should have two columns");
292 correlationSignal->GetColumn(0)->SetName(
"Time Offset [s]");
293 correlationSignal->GetColumn(1)->SetName(
"Computed Correlation");
301 if (correlationSignal->GetNumberOfColumns() != 2)
303 LOG_ERROR(
"Error in constructing the vtk tables that are to hold fine correlated signal. Table has " <<
304 correlationSignal->GetNumberOfColumns() <<
" columns, but should have two columns");
307 correlationSignal->GetColumn(0)->SetName(
"Time Offset [s]");
308 correlationSignal->GetColumn(1)->SetName(
"Computed Correlation (with fine step size)");
317 LOG_ERROR(
"Cannot get signal range, the signal is empty");
321 maxValue = *(std::max_element(signal.begin() + startIndex, signal.begin() + stopIndex));
322 minValue = *(std::min_element(signal.begin() + startIndex, signal.begin() + stopIndex));
330 if (signal.size() == 0)
332 LOG_ERROR(
"NormalizeMetricValues failed because the metric vector is empty");
338 stopIndex = signal.size() - 1;
343 for (
int i = startIndex;
i <= stopIndex; ++
i)
347 mu /= (stopIndex - startIndex + 1);
350 normalizationFactor = 1.0;
351 switch (METRIC_NORMALIZATION)
358 GetSignalRange(signal, startIndex, stopIndex, minValue, maxValue);
359 double maxPeakToPeak = fabs(maxValue - minValue);
360 if (maxPeakToPeak < 1e-10)
362 LOG_ERROR(
"Cannot normalize data, peak to peak difference is too small");
366 normalizationFactor = 1.0 / maxPeakToPeak;
374 for (
int i = startIndex;
i <= stopIndex; ++
i)
376 stdev += (signal.at(
i) - mu) * (signal.at(
i) - mu);
378 stdev = std::sqrt(stdev);
379 stdev /= std::sqrt(static_cast<double>(stopIndex - startIndex + 1) - 1);
383 LOG_ERROR(
"Cannot normalize data, stdev is too small");
387 normalizationFactor = 1.0 / stdev;
394 for (
unsigned int i = 0;
i < signal.size(); ++
i)
396 signal.at(
i) = (signal.at(
i) - mu) * normalizationFactor;
405 if (timestamps.size() == 0)
407 LOG_ERROR(
"NormalizeMetricValues failed because the metric vector is empty");
412 for (
unsigned int i = 0;
i < timestamps.size(); ++
i)
414 double t = timestamps.at(
i);
422 int stopIndex = timestamps.size() - 1;
423 for (
unsigned int i = timestamps.size() - 1;
i != 0; --
i)
425 double t = timestamps.at(
i);
439 const vtkSmartPointer<vtkPiecewiseFunction>& signalFunction, std::deque<double>& resampledSignalValues)
441 resampledSignalValues.resize(templateSignalTimestamps.size());
442 for (
unsigned int i = 0;
i < templateSignalTimestamps.size(); ++
i)
444 resampledSignalValues[
i] = signalFunction->GetValue(templateSignalTimestamps[
i]);
455 vtkSmartPointer<vtkPiecewiseFunction> trackerPositionPiecewiseSignal = vtkSmartPointer<vtkPiecewiseFunction>::New();
456 double midpoint = 0.5;
457 double sharpness = 0;
464 std::deque<double> normalizationFactors;
465 if (stepSizeSec < TIMESTAMP_EPSILON_SEC)
467 LOG_ERROR(
"Sampling resolution is too small: " << stepSizeSec <<
" sec");
471 std::deque<double> resampledTrackerPositionMetric;
473 corrTimeOffsets.clear();
474 for (
double offsetValueSec = minTrackerLagSec; offsetValueSec <= maxTrackerLagSec; offsetValueSec += stepSizeSec)
477 corrTimeOffsets.push_back(offsetValueSec);
478 for (
unsigned int i = 0;
i < slidingSignalTimestamps.size(); ++
i)
485 ResampleSignalLinearly(slidingSignalTimestamps, trackerPositionPiecewiseSignal, resampledTrackerPositionMetric);
486 double normalizationFactor = 1.0;
488 normalizationFactors.push_back(normalizationFactor);
494 bestCorrelationValue = corrValues.at(0);
495 bestCorrelationTimeOffset = corrTimeOffsets.at(0);
496 bestCorrelationNormalizationFactor = normalizationFactors.at(0);
497 for (
unsigned int i = 1;
i < corrValues.size(); ++
i)
499 if (corrValues.at(
i) > bestCorrelationValue)
501 bestCorrelationValue = corrValues.at(
i);
502 bestCorrelationTimeOffset = corrTimeOffsets.at(
i);
503 bestCorrelationNormalizationFactor = normalizationFactors.at(
i);
506 LOG_DEBUG(
"bestCorrelationValue=" << bestCorrelationValue);
507 LOG_DEBUG(
"bestCorrelationTimeOffset=" << bestCorrelationTimeOffset);
508 LOG_DEBUG(
"bestCorrelationNormalizationFactor=" << bestCorrelationNormalizationFactor);
509 LOG_DEBUG(
"numberOfSamples=" << corrValues.size());
514 if (signalA.size() != signalB.size())
516 LOG_ERROR(
"Cannot compute alignment metric: input signals size mismatch");
519 switch (SIGNAL_ALIGNMENT_METRIC)
525 for (
unsigned int i = 0;
i < signalA.size(); ++
i)
527 double diff = signalA.at(
i) - signalB.at(
i);
528 ssdSum -= diff * diff;
536 for (
unsigned int i = 0;
i < signalA.size(); ++
i)
538 xCorrSum += signalA.at(
i) * signalB.at(
i);
546 for (
unsigned int i = 0;
i < signalA.size(); ++
i)
548 sadSum -= fabs(signalA.at(
i) - signalB.at(
i));
553 LOG_ERROR(
"Unknown metric: " << SIGNAL_ALIGNMENT_METRIC);
566 vtkSmartPointer<vtkPlusPrincipalMotionDetectionAlgo> trackerDataMetricExtractor = vtkSmartPointer<vtkPlusPrincipalMotionDetectionAlgo>::New();
568 trackerDataMetricExtractor->SetTrackerFrames(signal.
frameList);
572 if (trackerDataMetricExtractor->Update() !=
PLUS_SUCCESS)
574 LOG_ERROR(
"Failed to get line positions from video frames");
578 trackerDataMetricExtractor->GetDetectedPositions(signal.
signalValues);
585 double maxPeakToPeak = std::abs(maxValue - minValue);
586 if (maxPeakToPeak < MINIMUM_TRACKER_SIGNAL_PEAK_TO_PEAK_MM)
588 LOG_ERROR(
"Detected metric values do not vary sufficiently (i.e. tracking signal is constant). Actual peak-to-peak variation: " << maxPeakToPeak <<
", expected minimum: " << MINIMUM_TRACKER_SIGNAL_PEAK_TO_PEAK_MM);
595 vtkSmartPointer<vtkPlusLineSegmentationAlgo> lineSegmenter = vtkSmartPointer<vtkPlusLineSegmentationAlgo>::New();
596 lineSegmenter->SetTrackedFrameList(*signal.
frameList);
603 LOG_ERROR(
"Failed to get line positions from video frames");
607 lineSegmenter->GetDetectedPositions(signal.
signalValues);
614 double maxPeakToPeak = std::abs(maxValue - minValue);
615 if (maxPeakToPeak < MINIMUM_VIDEO_SIGNAL_PEAK_TO_PEAK_PIXEL)
617 LOG_ERROR(
"Detected metric values do not vary sufficiently (i.e. video signal is constant)");
623 LOG_ERROR(
"Compute position signal value failed. Unknown frame type: " << signal.
frameType);
633 LOG_ERROR(
"Fixed signal frame list are empty");
638 LOG_ERROR(
"Moving signal frame list are empty");
647 double commonRangeMin = std::max(fixedTimestampMin, movingTimestampMin);
648 double commonRangeMax = std::min(fixedTimestampMax, movingTimestampMax);
651 LOG_ERROR(
"Insufficient overlap between fixed and moving frames timestamps to compute time offset (fixed: " << fixedTimestampMin <<
"-" << fixedTimestampMax <<
" sec, moving: " << movingTimestampMin <<
"-" << movingTimestampMax <<
" sec)");
679 LOG_ERROR(
"Failed to compute position signal from fixed frames");
685 LOG_ERROR(
"Failed to compute position signal from moving frames");
695 LOG_ERROR(
"Not enough fixed frames are available");
700 double searchRangeFineStep = imageFramePeriodSec * 3;
703 LOG_DEBUG(
"ComputeCorrelationBetweenFixedAndMovingSignal(sign convention #1)");
704 double bestCorrelationValue = 0;
705 double bestCorrelationTimeOffset = 0;
706 double bestCorrelationNormalizationFactor = 1.0;
707 std::deque<double> corrTimeOffsets;
708 std::deque<double> corrValues;
710 std::deque<double> corrTimeOffsetsFine;
711 std::deque<double> corrValuesFine;
713 LOG_DEBUG(
"Time offset with sign convention #1: " << bestCorrelationTimeOffset);
716 LOG_DEBUG(
"ComputeCorrelationBetweenFixedAndMovingSignal(sign convention #2)");
722 double bestCorrelationValueInvertedTracker(0);
723 double bestCorrelationTimeOffsetInvertedTracker(0);
724 double bestCorrelationNormalizationFactorInvertedTracker(1.0);
725 std::deque<double> corrTimeOffsetsInvertedTracker;
726 std::deque<double> corrValuesInvertedTracker;
731 bestCorrelationValueInvertedTracker,
732 bestCorrelationTimeOffsetInvertedTracker,
733 bestCorrelationNormalizationFactorInvertedTracker,
734 corrTimeOffsetsInvertedTracker,
735 corrValuesInvertedTracker
737 std::deque<double> corrTimeOffsetsInvertedTrackerFine;
738 std::deque<double> corrValuesInvertedTrackerFine;
740 bestCorrelationTimeOffsetInvertedTracker - searchRangeFineStep,
741 bestCorrelationTimeOffsetInvertedTracker + searchRangeFineStep,
743 bestCorrelationTimeOffsetInvertedTracker,
744 bestCorrelationNormalizationFactorInvertedTracker,
745 corrTimeOffsetsInvertedTrackerFine,
746 corrValuesInvertedTrackerFine
748 LOG_DEBUG(
"Time offset with sign convention #2: " << bestCorrelationTimeOffsetInvertedTracker);
751 if (std::abs(bestCorrelationTimeOffset) < std::abs(bestCorrelationTimeOffsetInvertedTracker))
770 this->
MovingLagSec = bestCorrelationTimeOffsetInvertedTracker;
793 double unusedNormFactor = 1.0;
798 LOG_DEBUG(
"Moving signal lags fixed signal by: " << this->
MovingLagSec <<
" [s]");
804 std::deque<double> shiftedSlidingSignalTimestamps;
813 vtkSmartPointer<vtkPiecewiseFunction> trackerPositionPiecewiseSignal = vtkSmartPointer<vtkPiecewiseFunction>::New();
814 double midpoint = 0.5;
815 double sharpness = 0;
821 std::deque<double> resampledNormalizedTrackerPositionMetric;
822 ResampleSignalLinearly(shiftedSlidingSignalTimestamps, trackerPositionPiecewiseSignal, resampledNormalizedTrackerPositionMetric);
825 for (
unsigned int i = 0;
i < resampledNormalizedTrackerPositionMetric.size(); ++
i)
847 LOG_ERROR(
"Calculated correlation exceeds threshold value. This may be an indicator of a poor calibration.");
851 LOG_DEBUG(
"Temporal calibration BestCorrelationValue = " << this->
BestCorrelationValue <<
" (threshold=" << SIGNAL_ALIGNMENT_METRIC_THRESHOLD[SIGNAL_ALIGNMENT_METRIC] <<
")");
859 double timeCorrection)
862 while (table->GetNumberOfColumns() > 0)
864 table->RemoveColumn(0);
868 vtkSmartPointer<vtkDoubleArray> arrX = vtkSmartPointer<vtkDoubleArray>::New();
869 table->AddColumn(arrX);
872 vtkSmartPointer<vtkDoubleArray> arrY = vtkSmartPointer<vtkDoubleArray>::New();
873 table->AddColumn(arrY);
876 table->SetNumberOfRows(
x.size());
877 for (
unsigned int i = 0;
i <
x.size(); ++
i)
879 table->SetValue(
i, 0,
x.at(
i) + timeCorrection);
880 table->SetValue(
i, 1,
y.at(
i));
889 XML_FIND_NESTED_ELEMENT_OPTIONAL(calibrationParameters, aConfig,
"vtkPlusTemporalCalibrationAlgo");
890 if (calibrationParameters == NULL)
892 LOG_DEBUG(
"vtkPlusTemporalCalibrationAlgo element is not defined");
896 XML_READ_SCALAR_ATTRIBUTE_OPTIONAL(
double, MaximumMovingLagSec, calibrationParameters);
898 if (calibrationParameters != NULL)
900 int clipOrigin[2] = {0};
901 int clipSize[2] = {0};
902 if (calibrationParameters->GetVectorAttribute(
"ClipRectangleOrigin", 2, clipOrigin) &&
903 calibrationParameters->GetVectorAttribute(
"ClipRectangleSize", 2, clipSize))
912 LOG_WARNING(
"Cannot find ClipRectangleOrigin or ClipRectangleSize attributes in the \'vtkPlusTemporalCalibrationAlgo\' configuration.");
931 std::vector<int> result;
PlusStatus GetCorrelationSignalFine(vtkTable *correlationSignal)
double signalTimeRangeMin
double BestCorrelationTimeOffset
PlusStatus GetUncalibratedMovingPositionSignal(vtkTable *unCalibratedMovingPositionSignal)
PlusStatus GetBestCorrelation(double &videoCorrelation)
void SetMaximumMovingLagSec(double maxLagSec)
std::string probeToReferenceTransformName
void SetFixedFrames(vtkIGSIOTrackedFrameList *frameList, FRAME_TYPE frameType)
int LineSegmentationClipRectangleOrigin[2]
PlusStatus ResampleSignalLinearly(const std::deque< double > &templateSignalTimestamps, const vtkSmartPointer< vtkPiecewiseFunction > &signalFunction, std::deque< double > &resampledSignalValues)
double BestCorrelationValue
PlusStatus GetCalibrationError(double &error)
void SetVideoClipRectangle(int *clipRectOriginIntVec, int *clipRectSizeIntVec)
std::deque< double > signalTimestamps
Singleton class providing tools needed for handling the configuration - finding files,...
bool SaveIntermediateImages
std::deque< double > CorrelationValuesFine
double ComputeAlignmentMetric(const std::deque< double > &signalA, const std::deque< double > &signalB)
void SetSaveIntermediateImages(bool saveIntermediateImages)
void SetFixedProbeToReferenceTransformName(const std::string &probeToReferenceTransformName)
double signalTimeRangeMax
std::vector< int > GetVideoClipRectangle() const
std::deque< double > signalValues
static vtkPlusConfig * GetInstance()
PlusStatus Update(TEMPORAL_CALIBRATION_ERROR &error)
std::deque< double > normalizedSignalValues
vtkStandardNewMacro(vtkPlusTemporalCalibrationAlgo)
void SetMovingProbeToReferenceTransformName(const std::string &probeToReferenceTransformName)
std::deque< double > CorrelationValues
double MaxCalibrationError
std::string GetOutputDirectory()
PlusStatus GetCorrelationSignal(vtkTable *correlationSignal)
PlusStatus GetCalibratedMovingPositionSignal(vtkTable *calibratedMovingPositionSignal)
double SamplingResolutionSec
vtkIGSIOTrackedFrameList * frameList
Computes the time lag between tracking streams or between a tracking and an ultrasound image stream.
void ComputeCorrelationBetweenFixedAndMovingSignal(double minTrackerLagSec, double maxTrackerLagSec, double stepSizeSec, double &bestCorrelationValue, double &bestCorrelationTimeOffset, double &bestCorrelationNormalizationFactor, std::deque< double > &corrTimeOffsets, std::deque< double > &corrValues)
std::string IntermediateFilesOutputDirectory
std::deque< double > CorrelationTimeOffsets
PlusStatus ComputeCommonTimeRange()
void SetMovingFrames(vtkIGSIOTrackedFrameList *frameList, FRAME_TYPE frameType)
void SetSamplingResolutionSec(double samplingResolutionSec)
PlusStatus GetSignalRange(const std::deque< double > &signal, int startIndex, int stopIndex, double &minValue, double &maxValue)
std::deque< double > normalizedSignalTimestamps
TEMPORAL_CALIBRATION_ERROR
std::deque< double > CalibrationErrorVector
int LineSegmentationClipRectangleSize[2]
void SetIntermediateFilesOutputDirectory(const std::string &outputDirectory)
double BestCorrelationNormalizationFactor
PlusStatus ReadConfiguration(vtkXMLDataElement *aConfig)
PlusStatus GetMaxCalibrationError(double &maxCalibrationError)
PlusStatus GetMovingLagSec(double &lag)
PlusStatus ComputeMovingSignalLagSec(TEMPORAL_CALIBRATION_ERROR &error)
std::deque< double > CorrelationTimeOffsetsFine
Direction vectors of rods y
PlusStatus ConstructTableSignal(std::deque< double > &x, std::deque< double > &y, vtkTable *table, double timeCorrection)
PlusStatus GetFixedPositionSignal(vtkTable *fixedPositionSignal)
SignalAlignmentMetricType
PlusStatus NormalizeMetricValues(std::deque< double > &signal, double &normalizationFactor, int startIndex=0, int stopIndex=-1)
PlusStatus ComputePositionSignalValues(SignalType &signal)