PlusLib  2.9.0
Software library for tracked ultrasound image acquisition, calibration, and processing.
vtkPlusTemporalCalibrationAlgo.cxx
Go to the documentation of this file.
1 /*=Plus=header=begin======================================================
2  Program: Plus
3  Copyright (c) Laboratory for Percutaneous Surgery. All rights reserved.
4  See License.txt for details.
5 =========================================================Plus=header=end*/
6 
7 #include "PlusConfigure.h"
8 #include "igsioTrackedFrame.h"
9 #include "vtkObjectFactory.h"
10 #include "vtkDoubleArray.h"
12 #include "vtkMath.h"
13 #include "vtkPiecewiseFunction.h"
15 #include "vtkTable.h"
17 #include "vtkIGSIOTrackedFrameList.h"
18 #include <algorithm>
19 #include <fstream>
20 #include <iostream>
21 
22 //-----------------------------------------------------------------------------
23 
25 
26 //-----------------------------------------------------------------------------
27 // Default algorithm parameters
28 namespace
29 {
30  // If the tracker metric "swings" less than this, abort
31  const double MINIMUM_TRACKER_SIGNAL_PEAK_TO_PEAK_MM = 8.0;
32  // If the video metric "swings" less than this, abort
33  const double MINIMUM_VIDEO_SIGNAL_PEAK_TO_PEAK_PIXEL = 30.0;
34  // Temporal resolution below which two time values are considered identical
35  const double TIMESTAMP_EPSILON_SEC = 0.0001;
36  // The maximum resolution that the user can request
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;
40 
42  {
43  SSD,
44  CORRELATION,
45  SAD,
46  SIGNAL_METRIC_TYPE_COUNT
47  };
48  const double SIGNAL_ALIGNMENT_METRIC_THRESHOLD[SIGNAL_METRIC_TYPE_COUNT] =
49  {
50  -2 ^ 500,
51  -2 ^ 500,
52  2 ^ 500
53  };
54  SignalAlignmentMetricType SIGNAL_ALIGNMENT_METRIC = SSD;
55 
57  {
58  STD,
59  AMPLITUDE
60  };
61  MetricNormalizationType METRIC_NORMALIZATION = STD;
62 }
63 
64 //-----------------------------------------------------------------------------
65 vtkPlusTemporalCalibrationAlgo::vtkPlusTemporalCalibrationAlgo()
66  : TrackerLagUpToDate(false)
67  , NeverUpdated(true)
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)
74  , MovingLagSec(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)
80 {
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;
87  this->IntermediateFilesOutputDirectory = vtkPlusConfig::GetInstance()->GetOutputDirectory();
88 }
89 
90 //-----------------------------------------------------------------------------
91 vtkPlusTemporalCalibrationAlgo::~vtkPlusTemporalCalibrationAlgo()
92 {
93  if (this->FixedSignal.frameList != NULL)
94  {
95  this->FixedSignal.frameList->UnRegister(NULL);
96  this->FixedSignal.frameList = NULL;
97  }
98  if (this->MovingSignal.frameList != NULL)
99  {
100  this->MovingSignal.frameList->UnRegister(NULL);
101  this->MovingSignal.frameList = NULL;
102  }
103 }
104 
105 //-----------------------------------------------------------------------------
107 {
109  {
110  return PLUS_FAIL;
111  }
113  return PLUS_SUCCESS;
114 }
115 
116 //-----------------------------------------------------------------------------
118 {
119  this->SaveIntermediateImages = saveIntermediateImages;
120 }
121 
122 //-----------------------------------------------------------------------------
123 void vtkPlusTemporalCalibrationAlgo::SetFixedFrames(vtkIGSIOTrackedFrameList* frameList, FRAME_TYPE frameType)
124 {
125  if (frameList != this->FixedSignal.frameList)
126  {
127  if (this->FixedSignal.frameList != NULL)
128  {
129  this->FixedSignal.frameList->UnRegister(NULL);
130  this->FixedSignal.frameList = NULL;
131  }
132  this->FixedSignal.frameList = frameList;
133  this->FixedSignal.frameList->Register(NULL);
134  }
135  this->FixedSignal.frameType = frameType;
136 }
137 
138 //-----------------------------------------------------------------------------
139 void vtkPlusTemporalCalibrationAlgo::SetFixedProbeToReferenceTransformName(const std::string& probeToReferenceTransformName)
140 {
141  this->FixedSignal.probeToReferenceTransformName = probeToReferenceTransformName;
142 }
143 
144 //-----------------------------------------------------------------------------
145 void vtkPlusTemporalCalibrationAlgo::SetMovingFrames(vtkIGSIOTrackedFrameList* frameList, FRAME_TYPE frameType)
146 {
147  if (frameList != this->MovingSignal.frameList)
148  {
149  if (this->MovingSignal.frameList != NULL)
150  {
151  this->MovingSignal.frameList->UnRegister(NULL);
152  this->MovingSignal.frameList = NULL;
153  }
154  this->MovingSignal.frameList = frameList;
155  this->MovingSignal.frameList->Register(NULL);
156  }
157  this->MovingSignal.frameType = frameType;
158 }
159 
160 //-----------------------------------------------------------------------------
161 void vtkPlusTemporalCalibrationAlgo::SetMovingProbeToReferenceTransformName(const std::string& probeToReferenceTransformName)
162 {
163  this->MovingSignal.probeToReferenceTransformName = probeToReferenceTransformName;
164 }
165 
166 //-----------------------------------------------------------------------------
168 {
169  if (samplingResolutionSec < MINIMUM_SAMPLING_RESOLUTION_SEC)
170  {
171  LOG_ERROR("Specified resampling resolution (" << samplingResolutionSec << " seconds) is too small. Sampling resolution must be greater than: " << MINIMUM_SAMPLING_RESOLUTION_SEC << " seconds");
172  return;
173  }
174  this->SamplingResolutionSec = samplingResolutionSec;
175 }
176 
177 //-----------------------------------------------------------------------------
179 {
180  this->MaxMovingLagSec = maxLagSec;
181 }
182 
183 //-----------------------------------------------------------------------------
185 {
186  this->IntermediateFilesOutputDirectory = outputDirectory;
187 }
188 
189 //-----------------------------------------------------------------------------
191 {
192  if (this->NeverUpdated)
193  {
194  LOG_ERROR("You must first call the \"Update()\" to compute the tracker lag.");
195  return PLUS_FAIL;
196  }
197  lag = this->MovingLagSec;
198  return PLUS_SUCCESS;
199 }
200 
201 //-----------------------------------------------------------------------------
203 {
204  if (this->NeverUpdated)
205  {
206  LOG_ERROR("You must first call the \"Update()\" to compute the best correlation metric.");
207  return PLUS_FAIL;
208  }
209  videoCorrelation = this->BestCorrelationValue;
210  return PLUS_SUCCESS;
211 }
212 
213 //-----------------------------------------------------------------------------
215 {
216  if (this->NeverUpdated)
217  {
218  LOG_ERROR("You must first call the \"Update()\" to compute the calibration error.");
219  return PLUS_FAIL;
220  }
221  error = this->CalibrationError;
222  return PLUS_SUCCESS;
223 }
224 
225 //-----------------------------------------------------------------------------
227 {
228  if (this->NeverUpdated)
229  {
230  LOG_ERROR("You must first call the \"Update()\" to compute the calibration error.");
231  return PLUS_FAIL;
232  }
234  return PLUS_SUCCESS;
235 }
236 
237 //-----------------------------------------------------------------------------
239 {
240  ConstructTableSignal(this->MovingSignal.normalizedSignalTimestamps, this->MovingSignal.normalizedSignalValues, unCalibratedMovingPositionSignal, 0);
241  if (unCalibratedMovingPositionSignal->GetNumberOfColumns() != 2)
242  {
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");
245  return PLUS_FAIL;
246  }
247  unCalibratedMovingPositionSignal->GetColumn(0)->SetName("Time [s]");
248  unCalibratedMovingPositionSignal->GetColumn(1)->SetName("Uncalibrated Moving Signal");
249  return PLUS_SUCCESS;
250 }
251 
252 //-----------------------------------------------------------------------------
254 {
255  ConstructTableSignal(this->MovingSignal.normalizedSignalTimestamps, this->MovingSignal.normalizedSignalValues, calibratedMovingPositionSignal, -this->MovingLagSec);
256  if (calibratedMovingPositionSignal->GetNumberOfColumns() != 2)
257  {
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");
260  return PLUS_FAIL;
261  }
262  calibratedMovingPositionSignal->GetColumn(0)->SetName("Time [s]");
263  calibratedMovingPositionSignal->GetColumn(1)->SetName("Calibrated Moving Signal");
264  return PLUS_SUCCESS;
265 }
266 
267 //-----------------------------------------------------------------------------
269 {
270  ConstructTableSignal(this->FixedSignal.signalTimestamps, this->FixedSignal.signalValues, fixedPositionSignal, 0);
271  if (fixedPositionSignal->GetNumberOfColumns() != 2)
272  {
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");
275  return PLUS_FAIL;
276  }
277  fixedPositionSignal->GetColumn(0)->SetName("Time [s]");
278  fixedPositionSignal->GetColumn(1)->SetName("Fixed Signal");
279  return PLUS_SUCCESS;
280 }
281 
282 //-----------------------------------------------------------------------------
284 {
285  ConstructTableSignal(this->CorrelationTimeOffsets, this->CorrelationValues, correlationSignal, 0);
286  if (correlationSignal->GetNumberOfColumns() != 2)
287  {
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");
290  return PLUS_FAIL;
291  }
292  correlationSignal->GetColumn(0)->SetName("Time Offset [s]");
293  correlationSignal->GetColumn(1)->SetName("Computed Correlation");
294  return PLUS_SUCCESS;
295 }
296 
297 //-----------------------------------------------------------------------------
299 {
300  ConstructTableSignal(this->CorrelationTimeOffsetsFine, this->CorrelationValuesFine, correlationSignal, 0);
301  if (correlationSignal->GetNumberOfColumns() != 2)
302  {
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");
305  return PLUS_FAIL;
306  }
307  correlationSignal->GetColumn(0)->SetName("Time Offset [s]");
308  correlationSignal->GetColumn(1)->SetName("Computed Correlation (with fine step size)");
309  return PLUS_SUCCESS;
310 }
311 
312 //-----------------------------------------------------------------------------
313 PlusStatus vtkPlusTemporalCalibrationAlgo::GetSignalRange(const std::deque<double>& signal, int startIndex, int stopIndex, double& minValue, double& maxValue)
314 {
315  if (signal.empty())
316  {
317  LOG_ERROR("Cannot get signal range, the signal is empty");
318  return PLUS_FAIL;
319  }
320  // Calculate maximum and minimum metric values
321  maxValue = *(std::max_element(signal.begin() + startIndex, signal.begin() + stopIndex));
322  minValue = *(std::min_element(signal.begin() + startIndex, signal.begin() + stopIndex));
323 
324  return PLUS_SUCCESS;
325 }
326 
327 //-----------------------------------------------------------------------------
328 PlusStatus vtkPlusTemporalCalibrationAlgo::NormalizeMetricValues(std::deque<double>& signal, double& normalizationFactor, int startIndex/*=0*/, int stopIndex/*=-1*/)
329 {
330  if (signal.size() == 0)
331  {
332  LOG_ERROR("NormalizeMetricValues failed because the metric vector is empty");
333  return PLUS_FAIL;
334  }
335 
336  if (stopIndex < 0)
337  {
338  stopIndex = signal.size() - 1;
339  }
340 
341  // Calculate the signal mean
342  double mu = 0;
343  for (int i = startIndex; i <= stopIndex; ++i)
344  {
345  mu += signal.at(i);
346  }
347  mu /= (stopIndex - startIndex + 1);
348 
349  // Calculate the signal "amplitude" and use its inverse as normalizationFactor
350  normalizationFactor = 1.0;
351  switch (METRIC_NORMALIZATION)
352  {
353  case AMPLITUDE:
354  {
355  // Divide by the maximum signal amplitude
356  double minValue = 0;
357  double maxValue = 0;
358  GetSignalRange(signal, startIndex, stopIndex, minValue, maxValue);
359  double maxPeakToPeak = fabs(maxValue - minValue);
360  if (maxPeakToPeak < 1e-10)
361  {
362  LOG_ERROR("Cannot normalize data, peak to peak difference is too small");
363  }
364  else
365  {
366  normalizationFactor = 1.0 / maxPeakToPeak;
367  }
368  break;
369  }
370  case STD:
371  {
372  // Calculate standard deviation
373  double stdev = 0;
374  for (int i = startIndex; i <= stopIndex; ++i)
375  {
376  stdev += (signal.at(i) - mu) * (signal.at(i) - mu);
377  }
378  stdev = std::sqrt(stdev);
379  stdev /= std::sqrt(static_cast<double>(stopIndex - startIndex + 1) - 1);
380 
381  if (stdev < 1e-10)
382  {
383  LOG_ERROR("Cannot normalize data, stdev is too small");
384  }
385  else
386  {
387  normalizationFactor = 1.0 / stdev;
388  }
389  break;
390  }
391  }
392 
393  // Normalize the signal values
394  for (unsigned int i = 0; i < signal.size(); ++i)
395  {
396  signal.at(i) = (signal.at(i) - mu) * normalizationFactor;
397  }
398 
399  return PLUS_SUCCESS;
400 }
401 
402 //-----------------------------------------------------------------------------
403 PlusStatus vtkPlusTemporalCalibrationAlgo::NormalizeMetricValues(std::deque<double>& signal, double& normalizationFactor, double startTime, double stopTime, const std::deque<double>& timestamps)
404 {
405  if (timestamps.size() == 0)
406  {
407  LOG_ERROR("NormalizeMetricValues failed because the metric vector is empty");
408  return PLUS_FAIL;
409  }
410 
411  int startIndex = 0;
412  for (unsigned int i = 0; i < timestamps.size(); ++i)
413  {
414  double t = timestamps.at(i);
415  if (t >= startTime)
416  {
417  startIndex = i;
418  break;
419  }
420  }
421 
422  int stopIndex = timestamps.size() - 1;
423  for (unsigned int i = timestamps.size() - 1; i != 0; --i)
424  {
425  double t = timestamps.at(i);
426  if (t <= stopTime)
427  {
428  stopIndex = i;
429  break;
430  }
431  }
432 
433  return NormalizeMetricValues(signal, normalizationFactor, startIndex, stopIndex);
434 }
435 
436 
437 //-----------------------------------------------------------------------------
438 PlusStatus vtkPlusTemporalCalibrationAlgo::ResampleSignalLinearly(const std::deque<double>& templateSignalTimestamps,
439  const vtkSmartPointer<vtkPiecewiseFunction>& signalFunction, std::deque<double>& resampledSignalValues)
440 {
441  resampledSignalValues.resize(templateSignalTimestamps.size());
442  for (unsigned int i = 0; i < templateSignalTimestamps.size(); ++i)
443  {
444  resampledSignalValues[i] = signalFunction->GetValue(templateSignalTimestamps[i]);
445  }
446  return PLUS_SUCCESS;
447 }
448 
449 //-----------------------------------------------------------------------------
450 void vtkPlusTemporalCalibrationAlgo::ComputeCorrelationBetweenFixedAndMovingSignal(double minTrackerLagSec, double maxTrackerLagSec, double stepSizeSec, double& bestCorrelationValue, double& bestCorrelationTimeOffset, double& bestCorrelationNormalizationFactor, std::deque<double>& corrTimeOffsets, std::deque<double>& corrValues)
451 {
452  // We will let the tracker metric be the "sliding" metric and let the video metric be the "fixed" metric. Since we are assuming a maximum offset between the two streams.
453 
454  // Construct piecewise function for tracker signal
455  vtkSmartPointer<vtkPiecewiseFunction> trackerPositionPiecewiseSignal = vtkSmartPointer<vtkPiecewiseFunction>::New();
456  double midpoint = 0.5;
457  double sharpness = 0;
458  for (unsigned int i = 0; i < this->MovingSignal.signalTimestamps.size(); ++i)
459  {
460  trackerPositionPiecewiseSignal->AddPoint(this->MovingSignal.signalTimestamps.at(i), this->MovingSignal.signalValues.at(i), midpoint, sharpness);
461  }
462 
463  // Compute alignment metric for each offset
464  std::deque<double> normalizationFactors;
465  if (stepSizeSec < TIMESTAMP_EPSILON_SEC)
466  {
467  LOG_ERROR("Sampling resolution is too small: " << stepSizeSec << " sec");
468  return;
469  }
470  std::deque<double> slidingSignalTimestamps(this->FixedSignal.signalTimestamps.size());
471  std::deque<double> resampledTrackerPositionMetric;
472  corrValues.clear();
473  corrTimeOffsets.clear();
474  for (double offsetValueSec = minTrackerLagSec; offsetValueSec <= maxTrackerLagSec; offsetValueSec += stepSizeSec)
475  {
476  //LOG_DEBUG("offsetValueSec = " << offsetValueSec);
477  corrTimeOffsets.push_back(offsetValueSec);
478  for (unsigned int i = 0; i < slidingSignalTimestamps.size(); ++i)
479  {
480  slidingSignalTimestamps.at(i) = this->FixedSignal.signalTimestamps.at(i) + offsetValueSec;
481  }
482 
483  NormalizeMetricValues(this->FixedSignal.signalValues, this->FixedSignalValuesNormalizationFactor, slidingSignalTimestamps.front(), slidingSignalTimestamps.back(), this->FixedSignal.signalTimestamps);
484 
485  ResampleSignalLinearly(slidingSignalTimestamps, trackerPositionPiecewiseSignal, resampledTrackerPositionMetric);
486  double normalizationFactor = 1.0;
487  NormalizeMetricValues(resampledTrackerPositionMetric, normalizationFactor);
488  normalizationFactors.push_back(normalizationFactor);
489 
490  corrValues.push_back(ComputeAlignmentMetric(this->FixedSignal.signalValues, resampledTrackerPositionMetric));
491  }
492 
493  // Find the time offset that has the best alignment metric value
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)
498  {
499  if (corrValues.at(i) > bestCorrelationValue)
500  {
501  bestCorrelationValue = corrValues.at(i);
502  bestCorrelationTimeOffset = corrTimeOffsets.at(i);
503  bestCorrelationNormalizationFactor = normalizationFactors.at(i);
504  }
505  }
506  LOG_DEBUG("bestCorrelationValue=" << bestCorrelationValue);
507  LOG_DEBUG("bestCorrelationTimeOffset=" << bestCorrelationTimeOffset);
508  LOG_DEBUG("bestCorrelationNormalizationFactor=" << bestCorrelationNormalizationFactor);
509  LOG_DEBUG("numberOfSamples=" << corrValues.size());
510 }
511 
512 double vtkPlusTemporalCalibrationAlgo::ComputeAlignmentMetric(const std::deque<double>& signalA, const std::deque<double>& signalB)
513 {
514  if (signalA.size() != signalB.size())
515  {
516  LOG_ERROR("Cannot compute alignment metric: input signals size mismatch");
517  return 0;
518  }
519  switch (SIGNAL_ALIGNMENT_METRIC)
520  {
521  case SSD:
522  {
523  // Use sum of squared differences as signal alignment metric
524  double ssdSum = 0;
525  for (unsigned int i = 0; i < signalA.size(); ++i)
526  {
527  double diff = signalA.at(i) - signalB.at(i); //SSD
528  ssdSum -= diff * diff;
529  }
530  return ssdSum;
531  }
532  case CORRELATION:
533  {
534  // Use correlation as signal alignment metric
535  double xCorrSum = 0;
536  for (unsigned int i = 0; i < signalA.size(); ++i)
537  {
538  xCorrSum += signalA.at(i) * signalB.at(i); // XCORR
539  }
540  return xCorrSum;
541  }
542  case SAD:
543  {
544  // Use sum of absolute differences as signal alignment metric
545  double sadSum = 0;
546  for (unsigned int i = 0; i < signalA.size(); ++i)
547  {
548  sadSum -= fabs(signalA.at(i) - signalB.at(i)); //SAD
549  }
550  return sadSum;
551  }
552  default:
553  LOG_ERROR("Unknown metric: " << SIGNAL_ALIGNMENT_METRIC);
554  }
555  return 0;
556 }
557 
558 
559 //-----------------------------------------------------------------------------
561 {
562  switch (signal.frameType)
563  {
564  case FRAME_TYPE_TRACKER:
565  {
566  vtkSmartPointer<vtkPlusPrincipalMotionDetectionAlgo> trackerDataMetricExtractor = vtkSmartPointer<vtkPlusPrincipalMotionDetectionAlgo>::New();
567 
568  trackerDataMetricExtractor->SetTrackerFrames(signal.frameList);
569  trackerDataMetricExtractor->SetSignalTimeRange(signal.signalTimeRangeMin, signal.signalTimeRangeMax);
570  trackerDataMetricExtractor->SetProbeToReferenceTransformName(signal.probeToReferenceTransformName);
571 
572  if (trackerDataMetricExtractor->Update() != PLUS_SUCCESS)
573  {
574  LOG_ERROR("Failed to get line positions from video frames");
575  return PLUS_FAIL;
576  }
577  trackerDataMetricExtractor->GetDetectedTimestamps(signal.signalTimestamps);
578  trackerDataMetricExtractor->GetDetectedPositions(signal.signalValues);
579 
580  // If the metric values do not "swing" sufficiently, the signal is considered constant--i.e. infinite period--and will
581  // not work for our purposes
582  double minValue = 0;
583  double maxValue = 0;
584  GetSignalRange(signal.signalValues, 0, signal.signalValues.size() - 1, minValue, maxValue);
585  double maxPeakToPeak = std::abs(maxValue - minValue);
586  if (maxPeakToPeak < MINIMUM_TRACKER_SIGNAL_PEAK_TO_PEAK_MM)
587  {
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);
589  return PLUS_FAIL;
590  }
591  return PLUS_SUCCESS;
592  }
593  case FRAME_TYPE_VIDEO:
594  {
595  vtkSmartPointer<vtkPlusLineSegmentationAlgo> lineSegmenter = vtkSmartPointer<vtkPlusLineSegmentationAlgo>::New();
596  lineSegmenter->SetTrackedFrameList(*signal.frameList);
597  lineSegmenter->SetClipRectangle(this->LineSegmentationClipRectangleOrigin, this->LineSegmentationClipRectangleSize);
598  lineSegmenter->SetSignalTimeRange(signal.signalTimeRangeMin, signal.signalTimeRangeMax);
599  lineSegmenter->SetSaveIntermediateImages(this->SaveIntermediateImages);
600  lineSegmenter->SetIntermediateFilesOutputDirectory(this->IntermediateFilesOutputDirectory);
601  if (lineSegmenter->Update() != PLUS_SUCCESS)
602  {
603  LOG_ERROR("Failed to get line positions from video frames");
604  return PLUS_FAIL;
605  }
606  lineSegmenter->GetDetectedTimestamps(signal.signalTimestamps);
607  lineSegmenter->GetDetectedPositions(signal.signalValues);
608 
609  // If the metric values do not "swing" sufficiently, the signal is considered constant--i.e. infinite period--and will
610  // not work for our purposes
611  double minValue = 0;
612  double maxValue = 0;
613  this->GetSignalRange(signal.signalValues, 0, signal.signalValues.size() - 1, minValue, maxValue);
614  double maxPeakToPeak = std::abs(maxValue - minValue);
615  if (maxPeakToPeak < MINIMUM_VIDEO_SIGNAL_PEAK_TO_PEAK_PIXEL)
616  {
617  LOG_ERROR("Detected metric values do not vary sufficiently (i.e. video signal is constant)");
618  return PLUS_FAIL;
619  }
620  return PLUS_SUCCESS;
621  }
622  default:
623  LOG_ERROR("Compute position signal value failed. Unknown frame type: " << signal.frameType);
624  return PLUS_FAIL;
625  }
626 }
627 
628 //-----------------------------------------------------------------------------
630 {
631  if (this->FixedSignal.frameList->GetNumberOfTrackedFrames() < 1)
632  {
633  LOG_ERROR("Fixed signal frame list are empty");
634  return PLUS_FAIL;
635  }
636  if (this->MovingSignal.frameList->GetNumberOfTrackedFrames() < 1)
637  {
638  LOG_ERROR("Moving signal frame list are empty");
639  return PLUS_FAIL;
640  }
641 
642  double fixedTimestampMin = this->FixedSignal.frameList->GetTrackedFrame(0)->GetTimestamp();
643  double fixedTimestampMax = this->FixedSignal.frameList->GetTrackedFrame(this->FixedSignal.frameList->GetNumberOfTrackedFrames() - 1)->GetTimestamp();;
644  double movingTimestampMin = this->MovingSignal.frameList->GetTrackedFrame(0)->GetTimestamp();
645  double movingTimestampMax = this->MovingSignal.frameList->GetTrackedFrame(this->MovingSignal.frameList->GetNumberOfTrackedFrames() - 1)->GetTimestamp();;
646 
647  double commonRangeMin = std::max(fixedTimestampMin, movingTimestampMin);
648  double commonRangeMax = std::min(fixedTimestampMax, movingTimestampMax);
649  if (commonRangeMin + this->MaxMovingLagSec >= commonRangeMax - this->MaxMovingLagSec)
650  {
651  LOG_ERROR("Insufficient overlap between fixed and moving frames timestamps to compute time offset (fixed: " << fixedTimestampMin << "-" << fixedTimestampMax << " sec, moving: " << movingTimestampMin << "-" << movingTimestampMax << " sec)");
652  return PLUS_FAIL;
653  }
654 
655  this->FixedSignal.signalTimeRangeMin = commonRangeMin;
656  this->FixedSignal.signalTimeRangeMax = commonRangeMax;
657  this->MovingSignal.signalTimeRangeMin = commonRangeMin + this->MaxMovingLagSec;
658  this->MovingSignal.signalTimeRangeMax = commonRangeMax - this->MaxMovingLagSec;
659 
660  return PLUS_SUCCESS;
661 }
662 
663 //-----------------------------------------------------------------------------
665 {
666  // Need to determine the common signal range before extracting signals from the frames,
667  // because normalization, PCA, etc. must be performed only by taking into account
668  // the frames in the common range.
670  {
672  return PLUS_FAIL;
673  }
674 
675  // Compute the position signal values from the input frames
677  {
679  LOG_ERROR("Failed to compute position signal from fixed frames");
680  return PLUS_FAIL;
681  }
683  {
685  LOG_ERROR("Failed to compute position signal from moving frames");
686  return PLUS_FAIL;
687  }
688 
689  // Compute approx image image frame period. We will use this frame period as a step size in the coarse optimum search phase.
690  double fixedTimestampMin = this->FixedSignal.signalTimestamps.at(0);
691  double fixedTimestampMax = this->FixedSignal.signalTimestamps.at(this->FixedSignal.signalTimestamps.size() - 1);
692  if (this->FixedSignal.signalTimestamps.size() < 2)
693  {
695  LOG_ERROR("Not enough fixed frames are available");
696  return PLUS_FAIL;
697  }
698  double imageFramePeriodSec = (fixedTimestampMax - fixedTimestampMin) / (this->FixedSignal.signalTimestamps.size() - 1);
699 
700  double searchRangeFineStep = imageFramePeriodSec * 3;
701 
702  // Compute cross correlation with sign convention #1
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;
709  ComputeCorrelationBetweenFixedAndMovingSignal(-this->MaxMovingLagSec, this->MaxMovingLagSec, imageFramePeriodSec, bestCorrelationValue, bestCorrelationTimeOffset, bestCorrelationNormalizationFactor, corrTimeOffsets, corrValues);
710  std::deque<double> corrTimeOffsetsFine;
711  std::deque<double> corrValuesFine;
712  ComputeCorrelationBetweenFixedAndMovingSignal(bestCorrelationTimeOffset - searchRangeFineStep, bestCorrelationTimeOffset + searchRangeFineStep, this->SamplingResolutionSec, bestCorrelationValue, bestCorrelationTimeOffset, bestCorrelationNormalizationFactor, corrTimeOffsetsFine, corrValuesFine);
713  LOG_DEBUG("Time offset with sign convention #1: " << bestCorrelationTimeOffset);
714 
715  // Compute cross correlation with sign convention #2
716  LOG_DEBUG("ComputeCorrelationBetweenFixedAndMovingSignal(sign convention #2)");
717  // Mirror tracker metric signal about x-axis
718  for (unsigned int i = 0; i < this->MovingSignal.signalValues.size(); ++i)
719  {
720  this->MovingSignal.signalValues.at(i) *= -1;
721  }
722  double bestCorrelationValueInvertedTracker(0);
723  double bestCorrelationTimeOffsetInvertedTracker(0);
724  double bestCorrelationNormalizationFactorInvertedTracker(1.0);
725  std::deque<double> corrTimeOffsetsInvertedTracker;
726  std::deque<double> corrValuesInvertedTracker;
728  -this->MaxMovingLagSec,
729  this->MaxMovingLagSec,
730  imageFramePeriodSec,
731  bestCorrelationValueInvertedTracker,
732  bestCorrelationTimeOffsetInvertedTracker,
733  bestCorrelationNormalizationFactorInvertedTracker,
734  corrTimeOffsetsInvertedTracker,
735  corrValuesInvertedTracker
736  );
737  std::deque<double> corrTimeOffsetsInvertedTrackerFine;
738  std::deque<double> corrValuesInvertedTrackerFine;
740  bestCorrelationTimeOffsetInvertedTracker - searchRangeFineStep,
741  bestCorrelationTimeOffsetInvertedTracker + searchRangeFineStep,
742  this->SamplingResolutionSec, bestCorrelationValueInvertedTracker,
743  bestCorrelationTimeOffsetInvertedTracker,
744  bestCorrelationNormalizationFactorInvertedTracker,
745  corrTimeOffsetsInvertedTrackerFine,
746  corrValuesInvertedTrackerFine
747  );
748  LOG_DEBUG("Time offset with sign convention #2: " << bestCorrelationTimeOffsetInvertedTracker);
749 
750  // Adopt the smallest tracker lag
751  if (std::abs(bestCorrelationTimeOffset) < std::abs(bestCorrelationTimeOffsetInvertedTracker))
752  {
753  this->MovingLagSec = bestCorrelationTimeOffset;
754  this->BestCorrelationTimeOffset = bestCorrelationTimeOffset;
755  this->BestCorrelationValue = bestCorrelationValue;
756  this->BestCorrelationNormalizationFactor = bestCorrelationNormalizationFactor;
757  this->CorrelationTimeOffsets = corrTimeOffsets;
758  this->CorrelationValues = corrValues;
759  this->CorrelationTimeOffsetsFine = corrTimeOffsetsFine;
760  this->CorrelationValuesFine = corrValuesFine;
761 
762  // Flip tracker metric signal back to correspond to sign convention #1
763  for (unsigned int i = 0; i < this->MovingSignal.signalValues.size(); ++i)
764  {
765  this->MovingSignal.signalValues.at(i) *= -1;
766  }
767  }
768  else
769  {
770  this->MovingLagSec = bestCorrelationTimeOffsetInvertedTracker;
771  this->BestCorrelationTimeOffset = bestCorrelationTimeOffsetInvertedTracker;
772  this->BestCorrelationValue = bestCorrelationValueInvertedTracker;
773  this->BestCorrelationNormalizationFactor = bestCorrelationNormalizationFactorInvertedTracker;
774  this->CorrelationTimeOffsets = corrTimeOffsetsInvertedTracker;
775  this->CorrelationValues = corrValuesInvertedTracker;
776  this->CorrelationTimeOffsetsFine = corrTimeOffsetsInvertedTrackerFine;
777  this->CorrelationValuesFine = corrValuesInvertedTrackerFine;
778  }
779 
780  // Normalize the tracker metric based on the best index offset (only considering the overlap "window"
781  this->MovingSignal.normalizedSignalValues.clear();
783  for (unsigned int i = 0; i < this->MovingSignal.signalTimestamps.size(); ++i)
784  {
786  {
789  }
790  }
791 
792  // Get a normalized tracker position metric that can be displayed
793  double unusedNormFactor = 1.0;
795 
796  this->CalibrationError = sqrt(-this->BestCorrelationValue) / this->BestCorrelationNormalizationFactor; // RMSE in mm
797 
798  LOG_DEBUG("Moving signal lags fixed signal by: " << this->MovingLagSec << " [s]");
799 
800 
801  // Get maximum calibration error
802 
803  // Get the timestamps of the sliding signal (i.e. cropped video signal) shifted by the best-found offset
804  std::deque<double> shiftedSlidingSignalTimestamps;
805  for (unsigned int i = 0; i < this->FixedSignal.signalTimestamps.size(); ++i)
806  {
807  shiftedSlidingSignalTimestamps.push_back(this->FixedSignal.signalTimestamps.at(i) + this->MovingLagSec); // TODO: check this
808  }
809 
810  // Get the values of the tracker metric at the offset sliding signal values
811 
812  // Construct piecewise function for tracker signal
813  vtkSmartPointer<vtkPiecewiseFunction> trackerPositionPiecewiseSignal = vtkSmartPointer<vtkPiecewiseFunction>::New();
814  double midpoint = 0.5;
815  double sharpness = 0;
816  for (unsigned int i = 0; i < this->MovingSignal.normalizedSignalTimestamps.size(); ++i)
817  {
818  trackerPositionPiecewiseSignal->AddPoint(this->MovingSignal.normalizedSignalTimestamps.at(i), this->MovingSignal.normalizedSignalValues.at(i), midpoint, sharpness);
819  }
820 
821  std::deque<double> resampledNormalizedTrackerPositionMetric;
822  ResampleSignalLinearly(shiftedSlidingSignalTimestamps, trackerPositionPiecewiseSignal, resampledNormalizedTrackerPositionMetric);
823 
824  this->CalibrationErrorVector.clear();
825  for (unsigned int i = 0; i < resampledNormalizedTrackerPositionMetric.size(); ++i)
826  {
827  double diff = resampledNormalizedTrackerPositionMetric.at(i) - this->FixedSignal.signalValues.at(i); //SSD
828  this->CalibrationErrorVector.push_back(diff * diff);
829  }
830 
831  this->MaxCalibrationError = 0;
832  for (unsigned int i = 0; i < this->CalibrationErrorVector.size(); ++i)
833  {
834  if (this->CalibrationErrorVector.at(i) > this->MaxCalibrationError)
835  {
836  this->MaxCalibrationError = this->CalibrationErrorVector.at(i);
837  }
838  }
839 
841 
842  this->NeverUpdated = false;
843 
844  if (this->BestCorrelationValue <= SIGNAL_ALIGNMENT_METRIC_THRESHOLD[SIGNAL_ALIGNMENT_METRIC])
845  {
847  LOG_ERROR("Calculated correlation exceeds threshold value. This may be an indicator of a poor calibration.");
848  return PLUS_FAIL;
849  }
850 
851  LOG_DEBUG("Temporal calibration BestCorrelationValue = " << this->BestCorrelationValue << " (threshold=" << SIGNAL_ALIGNMENT_METRIC_THRESHOLD[SIGNAL_ALIGNMENT_METRIC] << ")");
852  LOG_DEBUG("MaxCalibrationError=" << this->MaxCalibrationError);
853  LOG_DEBUG("CalibrationError=" << this->CalibrationError);
854  return PLUS_SUCCESS;
855 }
856 
857 //-----------------------------------------------------------------------------
858 PlusStatus vtkPlusTemporalCalibrationAlgo::ConstructTableSignal(std::deque<double>& x, std::deque<double>& y, vtkTable* table,
859  double timeCorrection)
860 {
861  // Clear table
862  while (table->GetNumberOfColumns() > 0)
863  {
864  table->RemoveColumn(0);
865  }
866 
867  // Create array corresponding to the time values of the tracker plot
868  vtkSmartPointer<vtkDoubleArray> arrX = vtkSmartPointer<vtkDoubleArray>::New();
869  table->AddColumn(arrX);
870 
871  // Create array corresponding to the metric values of the tracker plot
872  vtkSmartPointer<vtkDoubleArray> arrY = vtkSmartPointer<vtkDoubleArray>::New();
873  table->AddColumn(arrY);
874 
875  // Set the tracker data
876  table->SetNumberOfRows(x.size());
877  for (unsigned int i = 0; i < x.size(); ++i)
878  {
879  table->SetValue(i, 0, x.at(i) + timeCorrection);
880  table->SetValue(i, 1, y.at(i));
881  }
882 
883  return PLUS_SUCCESS;
884 }
885 
886 //-----------------------------------------------------------------------------
888 {
889  XML_FIND_NESTED_ELEMENT_OPTIONAL(calibrationParameters, aConfig, "vtkPlusTemporalCalibrationAlgo");
890  if (calibrationParameters == NULL)
891  {
892  LOG_DEBUG("vtkPlusTemporalCalibrationAlgo element is not defined");
893  return PLUS_SUCCESS;
894  }
895  XML_READ_BOOL_ATTRIBUTE_OPTIONAL(SaveIntermediateImages, calibrationParameters);
896  XML_READ_SCALAR_ATTRIBUTE_OPTIONAL(double, MaximumMovingLagSec, calibrationParameters);
897 
898  if (calibrationParameters != NULL)
899  {
900  int clipOrigin[2] = {0};
901  int clipSize[2] = {0};
902  if (calibrationParameters->GetVectorAttribute("ClipRectangleOrigin", 2, clipOrigin) &&
903  calibrationParameters->GetVectorAttribute("ClipRectangleSize", 2, clipSize))
904  {
905  this->LineSegmentationClipRectangleOrigin[0] = clipOrigin[0];
906  this->LineSegmentationClipRectangleOrigin[1] = clipOrigin[1];
907  this->LineSegmentationClipRectangleSize[0] = clipSize[0];
908  this->LineSegmentationClipRectangleSize[1] = clipSize[1];
909  }
910  else
911  {
912  LOG_WARNING("Cannot find ClipRectangleOrigin or ClipRectangleSize attributes in the \'vtkPlusTemporalCalibrationAlgo\' configuration.");
913  }
914  }
915 
916  return PLUS_SUCCESS;
917 }
918 
919 //-----------------------------------------------------------------------------
920 void vtkPlusTemporalCalibrationAlgo::SetVideoClipRectangle(int* clipRectOriginIntVec, int* clipRectSizeIntVec)
921 {
922  this->LineSegmentationClipRectangleOrigin[0] = clipRectOriginIntVec[0];
923  this->LineSegmentationClipRectangleOrigin[1] = clipRectOriginIntVec[1];
924  this->LineSegmentationClipRectangleSize[0] = clipRectSizeIntVec[0];
925  this->LineSegmentationClipRectangleSize[1] = clipRectSizeIntVec[1];
926 }
927 
928 //----------------------------------------------------------------------------
930 {
931  std::vector<int> result;
932  result.push_back(this->LineSegmentationClipRectangleOrigin[0]);
933  result.push_back(this->LineSegmentationClipRectangleOrigin[1]);
934  result.push_back(this->LineSegmentationClipRectangleSize[0]);
935  result.push_back(this->LineSegmentationClipRectangleSize[1]);
936  return result;
937 }
PlusStatus GetCorrelationSignalFine(vtkTable *correlationSignal)
PlusStatus GetUncalibratedMovingPositionSignal(vtkTable *unCalibratedMovingPositionSignal)
PlusStatus GetBestCorrelation(double &videoCorrelation)
void SetFixedFrames(vtkIGSIOTrackedFrameList *frameList, FRAME_TYPE frameType)
PlusStatus ResampleSignalLinearly(const std::deque< double > &templateSignalTimestamps, const vtkSmartPointer< vtkPiecewiseFunction > &signalFunction, std::deque< double > &resampledSignalValues)
igsioStatus PlusStatus
Definition: PlusCommon.h:40
void SetVideoClipRectangle(int *clipRectOriginIntVec, int *clipRectSizeIntVec)
Singleton class providing tools needed for handling the configuration - finding files,...
Definition: vtkPlusConfig.h:24
for i
double ComputeAlignmentMetric(const std::deque< double > &signalA, const std::deque< double > &signalB)
void SetSaveIntermediateImages(bool saveIntermediateImages)
void SetFixedProbeToReferenceTransformName(const std::string &probeToReferenceTransformName)
#define PLUS_FAIL
Definition: PlusCommon.h:43
static vtkPlusConfig * GetInstance()
PlusStatus Update(TEMPORAL_CALIBRATION_ERROR &error)
vtkStandardNewMacro(vtkPlusTemporalCalibrationAlgo)
void SetMovingProbeToReferenceTransformName(const std::string &probeToReferenceTransformName)
#define PLUS_SUCCESS
Definition: PlusCommon.h:44
std::string GetOutputDirectory()
PlusStatus GetCorrelationSignal(vtkTable *correlationSignal)
PlusStatus GetCalibratedMovingPositionSignal(vtkTable *calibratedMovingPositionSignal)
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)
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)
int x
Definition: phidget22.h:4265
void SetIntermediateFilesOutputDirectory(const std::string &outputDirectory)
PlusStatus ReadConfiguration(vtkXMLDataElement *aConfig)
PlusStatus GetMaxCalibrationError(double &maxCalibrationError)
PlusStatus ComputeMovingSignalLagSec(TEMPORAL_CALIBRATION_ERROR &error)
Direction vectors of rods y
Definition: algo3.m:15
PlusStatus ConstructTableSignal(std::deque< double > &x, std::deque< double > &y, vtkTable *table, double timeCorrection)
for t
Definition: exploreFolders.m:9
PlusStatus GetFixedPositionSignal(vtkTable *fixedPositionSignal)
PlusStatus NormalizeMetricValues(std::deque< double > &signal, double &normalizationFactor, int startIndex=0, int stopIndex=-1)
PlusStatus ComputePositionSignalValues(SignalType &signal)
maxCalibrationError