PlusLib  2.9.0
Software library for tracked ultrasound image acquisition, calibration, and processing.
TemporalCalibration.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 
20 // Local includes
21 #include "PlusConfigure.h"
22 #include "igsioTrackedFrame.h"
23 #include "vtkIGSIOSequenceIO.h"
25 #include "vtkIGSIOTrackedFrameList.h"
26 
27 // VTK includes
28 #include <vtkAxis.h>
29 #include <vtkChartXY.h>
30 #include <vtkContextScene.h>
31 #ifdef PLUS_RENDERING_ENABLED
32 #include <vtkContextView.h>
33 #endif
34 #include <vtkPNGWriter.h>
35 #include <vtkPlot.h>
36 #include <vtkRenderWindow.h>
37 #include <vtkRenderer.h>
38 #include <vtkTable.h>
39 #include <vtkWindowToImageFilter.h>
40 #include <vtkXMLDataElement.h>
41 #include <vtkXMLUtilities.h>
42 #include <vtksys/CommandLineArguments.hxx>
43 
44 // define tolerance used for comparing double numbers
45 namespace
46 {
47  const double MAX_ALLOWED_TIME_LAG_DIFF_SEC = 0.005;
48  const double MAX_ALLOWED_ERROR_DIFF = 1.0;
49 
50  struct TemporalCalibrationResult
51  {
52  double trackerLagSec;
53  double calibrationError;
54  double maxCalibrationError;
55 
56  TemporalCalibrationResult()
57  : trackerLagSec(0)
58  , calibrationError(0)
60  {}
61  };
62 }
63 
64 //----------------------------------------------------------------------------
65 void SaveMetricPlot(const char* filename, vtkTable* videoPositionMetric, vtkTable* trackerPositionMetric, std::string& xAxisLabel,
66  std::string& yAxisLabel)
67 {
68 #ifdef PLUS_RENDERING_ENABLED
69  // Set up the view
70  vtkSmartPointer<vtkContextView> view = vtkSmartPointer<vtkContextView>::New();
71  view->GetRenderer()->SetBackground(1.0, 1.0, 1.0);
72  vtkSmartPointer<vtkChartXY> chart = vtkSmartPointer<vtkChartXY>::New();
73  view->GetScene()->AddItem(chart);
74 
75  // Add the two line plots
76  vtkPlot* videoPositionMetricLine = chart->AddPlot(vtkChart::LINE);
77  videoPositionMetricLine->SetInputData(videoPositionMetric, 0, 1);
78  videoPositionMetricLine->SetColor(0, 0, 1);
79  videoPositionMetricLine->SetWidth(1.0);
80 
81  vtkPlot* trackerMetricLine = chart->AddPlot(vtkChart::LINE);
82  trackerMetricLine->SetInputData(trackerPositionMetric, 0, 1);
83  trackerMetricLine->SetColor(0, 1, 0);
84  trackerMetricLine->SetWidth(1.0);
85  chart->SetShowLegend(true);
86  chart->GetAxis(vtkAxis::LEFT)->SetTitle(yAxisLabel.c_str());
87  chart->GetAxis(vtkAxis::BOTTOM)->SetTitle(xAxisLabel.c_str());
88 
89  // Render plot and save it to file
90  vtkSmartPointer<vtkRenderWindow> renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
91  renderWindow->AddRenderer(view->GetRenderer());
92  renderWindow->SetSize(1600, 1200);
93  renderWindow->OffScreenRenderingOn();
94 
95  vtkSmartPointer<vtkWindowToImageFilter> windowToImageFilter = vtkSmartPointer<vtkWindowToImageFilter>::New();
96  windowToImageFilter->SetInput(renderWindow);
97  windowToImageFilter->Update();
98 
99  vtkSmartPointer<vtkPNGWriter> writer = vtkSmartPointer<vtkPNGWriter>::New();
100  writer->SetFileName(filename);
101  writer->SetInputData(windowToImageFilter->GetOutput());
102  writer->Write();
103 #else
104  LOG_ERROR("Function not available when VTK_RENDERING_BACKEND is None!");
105 #endif
106 }
107 
108 //----------------------------------------------------------------------------
109 void WriteCalibrationResultToFile(const std::string& outputFileName, const TemporalCalibrationResult& calibResult)
110 {
111  std::ofstream myfile;
112  myfile.open(outputFileName.c_str());
113  myfile << "<TemporalCalibrationResults TrackerLagSec=\"" << calibResult.trackerLagSec
114  << "\" CalibrationError=\"" << calibResult.calibrationError
115  << "\" MaxCalibrationError=\"" << calibResult.maxCalibrationError << "\" />";
116  myfile.close();
117 }
118 
119 //----------------------------------------------------------------------------
120 PlusStatus ReadCalibrationResultsFromFile(const std::string& resultSaveFilename, TemporalCalibrationResult& calibResult)
121 {
122  calibResult.calibrationError = 0.0;
123  calibResult.maxCalibrationError = 0.0;
124  calibResult.trackerLagSec = 0.0;
125 
126  if (resultSaveFilename.empty())
127  {
128  LOG_ERROR("Cannot read line calibration results, filename is empty");
129  return PLUS_FAIL;
130  }
131  vtkSmartPointer<vtkXMLDataElement> resultsElem = vtkSmartPointer<vtkXMLDataElement>::Take(vtkXMLUtilities::ReadElementFromFile(resultSaveFilename.c_str()));
132  if (resultsElem == NULL)
133  {
134  LOG_ERROR("Failed to read baseline file: " << resultSaveFilename);
135  return PLUS_FAIL;
136  }
137  if (resultsElem->GetName() == NULL || STRCASECMP(resultsElem->GetName(), "TemporalCalibrationResults") != 0)
138  {
139  LOG_ERROR("Unable to find TemporalCalibrationResults XML data element in baseline: " << resultSaveFilename);
140  return PLUS_FAIL;
141  }
142  PlusStatus status = PLUS_SUCCESS;
143  if (!resultsElem->GetScalarAttribute("CalibrationError", calibResult.calibrationError))
144  {
145  LOG_ERROR("Unable to find CalibrationError attribute in TemporalCalibrationResults element");
146  status = PLUS_FAIL;
147  }
148  if (!resultsElem->GetScalarAttribute("MaxCalibrationError", calibResult.maxCalibrationError))
149  {
150  LOG_ERROR("Unable to find MaxCalibrationError attribute in TemporalCalibrationResults element");
151  status = PLUS_FAIL;
152  }
153  if (!resultsElem->GetScalarAttribute("TrackerLagSec", calibResult.trackerLagSec))
154  {
155  LOG_ERROR("Unable to find TrackerLagSec attribute in TemporalCalibrationResults element");
156  status = PLUS_FAIL;
157  }
158  return status;
159 }
160 
161 //----------------------------------------------------------------------------
162 int CompareCalibrationResults(const TemporalCalibrationResult& calibResult, const TemporalCalibrationResult& baselineCalibResult)
163 {
164  int numberOfFailures = 0;
165 
166  if (fabs(calibResult.trackerLagSec - baselineCalibResult.trackerLagSec) > MAX_ALLOWED_TIME_LAG_DIFF_SEC)
167  {
168  LOG_ERROR("TrackerLagSec comparison error: current=" << calibResult.trackerLagSec << ", baseline=" << baselineCalibResult.trackerLagSec);
169  ++numberOfFailures;
170  }
171  if (fabs(calibResult.calibrationError - baselineCalibResult.calibrationError) > MAX_ALLOWED_ERROR_DIFF)
172  {
173  LOG_ERROR("CalibrationError comparison error: current=" << calibResult.calibrationError << ", baseline=" << baselineCalibResult.calibrationError);
174  ++numberOfFailures;
175  }
176  if (fabs(calibResult.maxCalibrationError - baselineCalibResult.maxCalibrationError) > MAX_ALLOWED_ERROR_DIFF)
177  {
178  LOG_ERROR("MaxCalibrationError comparison error: current=" << calibResult.maxCalibrationError << ", baseline=" << baselineCalibResult.maxCalibrationError);
179  ++numberOfFailures;
180  }
181 
182  return numberOfFailures;
183 }
184 
185 //----------------------------------------------------------------------------
186 int main(int argc, char** argv)
187 {
188  bool printHelp(false);
189  bool plotResults(false);
190  bool saveIntermediateImages(false);
191  int verboseLevel = vtkPlusLogger::LOG_LEVEL_UNDEFINED;
192  std::string inputMovingSequenceMetafile("");
193  std::string inputFixedSequenceMetafile("");
194  std::string intermediateFileOutputDirectory; // Directory into which the intermediate files are written
195  double samplingResolutionSec = 0.001; // Resolution used for re-sampling [s]
196  std::string fixedProbeToReferenceTransformNameStr("");
197  std::string movingProbeToReferenceTransformNameStr("");
198  double maxTimeOffsetSec = 0.5;
199  std::vector<int> clipRectOrigin;
200  std::vector<int> clipRectSize;
201  std::string inputBaselineFileName;
202 
203  vtksys::CommandLineArguments args;
204  args.Initialize(argc, argv);
205 
206  args.AddArgument("--help", vtksys::CommandLineArguments::NO_ARGUMENT, &printHelp, "Print this help.");
207  args.AddArgument("--fixed-seq-file", vtksys::CommandLineArguments::EQUAL_ARGUMENT, &inputFixedSequenceMetafile, "Input US image sequence metafile name with path");
208  args.AddArgument("--moving-seq-file", vtksys::CommandLineArguments::EQUAL_ARGUMENT, &inputMovingSequenceMetafile, "Input tracker sequence metafile name with path");
209  args.AddArgument("--fixed-probe-to-reference-transform", vtksys::CommandLineArguments::EQUAL_ARGUMENT, &fixedProbeToReferenceTransformNameStr, "If specified then it indicates that the fixed sequence contains tracking data and it specifies the transform name that describes the probe pose relative to a static reference (for example, ProbeToReference). If not specified then video data is used from the fixed sequence.");
210  args.AddArgument("--moving-probe-to-reference-transform", vtksys::CommandLineArguments::EQUAL_ARGUMENT, &movingProbeToReferenceTransformNameStr, "If specified then it indicates that the moving sequence contains tracking data and it specifies the transform name that describes the probe pose relative to a static reference (for example, ProbeToReference). If not specified then video data is used from the moving sequence.");
211  args.AddArgument("--max-time-offset-sec", vtksys::CommandLineArguments::EQUAL_ARGUMENT, &maxTimeOffsetSec, "Maximum time offset between the signals, in seconds (default: 2 seconds)");
212  args.AddArgument("--plot-results", vtksys::CommandLineArguments::NO_ARGUMENT, &plotResults, "Plot results (display position vs. time plots without and with temporal calibration)");
213  args.AddArgument("--verbose", vtksys::CommandLineArguments::EQUAL_ARGUMENT, &verboseLevel, "Verbose level (1=error only, 2=warning, 3=info, 4=debug, 5=trace)");
214  args.AddArgument("--sampling-resolution-sec", vtksys::CommandLineArguments::EQUAL_ARGUMENT, &samplingResolutionSec, "Sampling resolution (in seconds, default is 0.001)");
215  args.AddArgument("--save-intermediate-images", vtksys::CommandLineArguments::NO_ARGUMENT, &saveIntermediateImages, "Save images of intermediate steps (scanlines used, and detected lines)");
216  args.AddArgument("--intermediate-file-output-dir", vtksys::CommandLineArguments::EQUAL_ARGUMENT, &intermediateFileOutputDirectory, "Directory into which the intermediate files are written");
217  args.AddArgument("--clip-rect-origin", vtksys::CommandLineArguments::MULTI_ARGUMENT, &clipRectOrigin, "Origin of the clipping rectangle");
218  args.AddArgument("--clip-rect-size", vtksys::CommandLineArguments::MULTI_ARGUMENT, &clipRectSize, "Size of the clipping rectangle");
219  args.AddArgument("--baseline-file", vtksys::CommandLineArguments::EQUAL_ARGUMENT, &inputBaselineFileName, "Input xml baseline file name with path");
220 
221  if (!args.Parse())
222  {
223  std::cerr << "Problem parsing arguments" << std::endl;
224  std::cout << "Help: " << args.GetHelp() << std::endl;
225  exit(EXIT_FAILURE);
226  }
227 
228  if (printHelp)
229  {
230  std::cout << args.GetHelp() << std::endl;
231  exit(EXIT_SUCCESS);
232  }
233 
234  vtkPlusLogger::Instance()->SetLogLevel(verboseLevel);
235 
236  if (inputMovingSequenceMetafile.empty())
237  {
238  std::cerr << "input-tracker-sequence-metafile required argument!" << std::endl;
239  std::cout << "Help: " << args.GetHelp() << std::endl;
240  exit(EXIT_FAILURE);
241  }
242 
243  if (inputFixedSequenceMetafile.empty())
244  {
245  std::cerr << "input-video-sequence-metafile required argument!" << std::endl;
246  std::cout << "Help: " << args.GetHelp() << std::endl;
247  exit(EXIT_FAILURE);
248  }
249 
250 
252 
253  if (intermediateFileOutputDirectory.empty())
254  {
255  intermediateFileOutputDirectory = vtkPlusConfig::GetInstance()->GetOutputDirectory();
256  }
257 
258  vtkPlusTemporalCalibrationAlgo* testTemporalCalibrationObject = vtkPlusTemporalCalibrationAlgo::New();
261  vtkSmartPointer<vtkIGSIOTrackedFrameList> movingFrames = vtkSmartPointer<vtkIGSIOTrackedFrameList>::New();
262  vtkSmartPointer<vtkIGSIOTrackedFrameList> fixedFrames = vtkSmartPointer<vtkIGSIOTrackedFrameList>::New();
263  if (!fixedProbeToReferenceTransformNameStr.empty())
264  {
265  fixedFrames->SetValidationRequirements(REQUIRE_UNIQUE_TIMESTAMP | REQUIRE_TRACKING_OK);
266  testTemporalCalibrationObject->SetFixedProbeToReferenceTransformName(fixedProbeToReferenceTransformNameStr);
268  LOG_DEBUG("Fixed data type: tracking");
269  }
270  else
271  {
272  fixedFrames->SetValidationRequirements(REQUIRE_UNIQUE_TIMESTAMP);
274  LOG_DEBUG("Fixed data type: video");
275  }
276 
277  if (!movingProbeToReferenceTransformNameStr.empty())
278  {
279  movingFrames->SetValidationRequirements(REQUIRE_UNIQUE_TIMESTAMP | REQUIRE_TRACKING_OK);
280  testTemporalCalibrationObject->SetMovingProbeToReferenceTransformName(movingProbeToReferenceTransformNameStr);
282  LOG_DEBUG("Moving data type: video");
283 
284  }
285  else
286  {
287  movingFrames->SetValidationRequirements(REQUIRE_UNIQUE_TIMESTAMP);
289  LOG_DEBUG("Moving data type: tracking");
290  }
291 
292  // Read fixed frames
293  LOG_DEBUG("Read fixed data from " << inputFixedSequenceMetafile);
294  if (vtkIGSIOSequenceIO::Read(inputFixedSequenceMetafile, fixedFrames) != PLUS_SUCCESS)
295  {
296  LOG_ERROR("Failed to read fixed data from sequence metafile: " << inputFixedSequenceMetafile << ". Exiting...");
297  exit(EXIT_FAILURE);
298  }
299  testTemporalCalibrationObject->SetFixedFrames(fixedFrames, fixedType);
300 
301  // Read moving frames
302  LOG_DEBUG("Read moving data from " << inputMovingSequenceMetafile);
303  if (vtkIGSIOSequenceIO::Read(inputMovingSequenceMetafile, movingFrames) != PLUS_SUCCESS)
304  {
305  LOG_ERROR("Failed to read moving data from sequence metafile: " << inputMovingSequenceMetafile << ". Exiting...");
306  exit(EXIT_FAILURE);
307  }
308  testTemporalCalibrationObject->SetMovingFrames(movingFrames, movingType);
309 
310  // Create temporal calibration object; Set pertinent parameters
311  testTemporalCalibrationObject->SetSamplingResolutionSec(samplingResolutionSec);
312  testTemporalCalibrationObject->SetSaveIntermediateImages(saveIntermediateImages);
313  testTemporalCalibrationObject->SetIntermediateFilesOutputDirectory(intermediateFileOutputDirectory);
314  testTemporalCalibrationObject->SetMaximumMovingLagSec(maxTimeOffsetSec);
315 
316  if (clipRectOrigin.size() > 0 || clipRectSize.size() > 0)
317  {
318  if (clipRectOrigin.size() != 2 || clipRectSize.size() != 2)
319  {
320  LOG_ERROR("Invalid clip rectangle origin and/or size");
321  exit(EXIT_FAILURE);
322  }
323  int clipRectOriginIntVec[2] = { clipRectOrigin[0], clipRectOrigin[1] };
324  int clipRectSizeIntVec[2] = { clipRectSize[0], clipRectSize[1] };
325  testTemporalCalibrationObject->SetVideoClipRectangle(clipRectOriginIntVec, clipRectSizeIntVec);
326  }
327 
329 
330  // Calculate the time-offset
331  if (testTemporalCalibrationObject->Update(error) != PLUS_SUCCESS)
332  {
333  LOG_ERROR("Cannot determine tracker lag, temporal calibration failed");
334  exit(EXIT_FAILURE);
335  }
336 
337  // Display results
338  TemporalCalibrationResult calibResult;
339  if (testTemporalCalibrationObject->GetMovingLagSec(calibResult.trackerLagSec) != PLUS_SUCCESS)
340  {
341  LOG_ERROR("Cannot determine tracker lag, temporal calibration failed");
342  exit(EXIT_FAILURE);
343  }
344  LOG_INFO("Tracker lag: " << calibResult.trackerLagSec << " sec (>0 if the tracker data lags)");
345  if (testTemporalCalibrationObject->GetCalibrationError(calibResult.calibrationError) != PLUS_SUCCESS)
346  {
347  LOG_ERROR("Cannot determine calibration error, temporal calibration failed");
348  exit(EXIT_FAILURE);
349  }
350  LOG_INFO("Calibration error: " << calibResult.calibrationError);
351  if (testTemporalCalibrationObject->GetMaxCalibrationError(calibResult.maxCalibrationError) != PLUS_SUCCESS)
352  {
353  LOG_ERROR("Cannot determine max calibration error, temporal calibration failed");
354  exit(EXIT_FAILURE);
355  }
356  LOG_INFO("Max calibration error: " << calibResult.maxCalibrationError);
357 
358  // Write results to file
359  std::ostringstream trackerLagOutputFilename;
360  trackerLagOutputFilename << intermediateFileOutputDirectory << "/TemporalCalibrationResults.xml" << std::ends;
361  WriteCalibrationResultToFile(trackerLagOutputFilename.str(), calibResult);
362 
363  if (plotResults)
364  {
365  vtkSmartPointer<vtkTable> videoPositionMetric = vtkSmartPointer<vtkTable>::New();
366  testTemporalCalibrationObject->GetFixedPositionSignal(videoPositionMetric);
367 
368  // Uncalibrated
369  vtkSmartPointer<vtkTable> uncalibratedTrackerPositionMetric = vtkSmartPointer<vtkTable>::New();
370  testTemporalCalibrationObject->GetUncalibratedMovingPositionSignal(uncalibratedTrackerPositionMetric);
371  std::string filename = intermediateFileOutputDirectory + "/MetricPlotUncalibrated.png";
372 
373  std::string xLabel = "Time [s]";
374  std::string yLabel = "Position Metric";
375  SaveMetricPlot(filename.c_str(), videoPositionMetric, uncalibratedTrackerPositionMetric, xLabel, yLabel);
376 
377  // Calibrated
378  vtkSmartPointer<vtkTable> calibratedTrackerPositionMetric = vtkSmartPointer<vtkTable>::New();
379  testTemporalCalibrationObject->GetCalibratedMovingPositionSignal(calibratedTrackerPositionMetric);
380  filename = intermediateFileOutputDirectory + "/MetricPlotCalibrated.png";
381  SaveMetricPlot(filename.c_str(), videoPositionMetric, calibratedTrackerPositionMetric, xLabel, yLabel);
382 
383  // Correlation Signal
384  vtkSmartPointer<vtkTable> correlationSignal = vtkSmartPointer<vtkTable>::New();
385  testTemporalCalibrationObject->GetCorrelationSignal(correlationSignal);
386  vtkSmartPointer<vtkTable> correlationSignalFine = vtkSmartPointer<vtkTable>::New();
387  testTemporalCalibrationObject->GetCorrelationSignalFine(correlationSignalFine);
388  filename = intermediateFileOutputDirectory + "/CorrelationSignal.png";
389  xLabel = "Tracker Offset [s]";
390  yLabel = "Correlation Value";
391  SaveMetricPlot(filename.c_str(), correlationSignal, correlationSignalFine, xLabel, yLabel);
392  }
393 
394  // Compare result to baseline
395  if (!inputBaselineFileName.empty())
396  {
397  LOG_INFO("Comparing result with baseline...");
398  TemporalCalibrationResult baselineCalibResult;
399  if (ReadCalibrationResultsFromFile(inputBaselineFileName, baselineCalibResult) != PLUS_SUCCESS)
400  {
401  LOG_ERROR("Failed to read baseline data file");
402  exit(EXIT_FAILURE);
403  }
404  int numberOfFailures = CompareCalibrationResults(calibResult, baselineCalibResult);
405  if (numberOfFailures > 0)
406  {
407  LOG_ERROR("Number of differences compared to baseline: " << numberOfFailures << ". Test failed!");
408  exit(EXIT_FAILURE);
409  }
410  LOG_INFO("Baseline comparison completed successfully");
411  }
412 
413  testTemporalCalibrationObject->Delete();
414 
415  return EXIT_SUCCESS;
416 }
PlusStatus GetCorrelationSignalFine(vtkTable *correlationSignal)
PlusStatus GetUncalibratedMovingPositionSignal(vtkTable *unCalibratedMovingPositionSignal)
static vtkPlusTemporalCalibrationAlgo * New()
int main(int argc, char **argv)
void SetFixedFrames(vtkIGSIOTrackedFrameList *frameList, FRAME_TYPE frameType)
calibrationError
igsioStatus PlusStatus
Definition: PlusCommon.h:40
void SetVideoClipRectangle(int *clipRectOriginIntVec, int *clipRectSizeIntVec)
int CompareCalibrationResults(const TemporalCalibrationResult &calibResult, const TemporalCalibrationResult &baselineCalibResult)
void SaveMetricPlot(const char *filename, vtkTable *videoPositionMetric, vtkTable *trackerPositionMetric, std::string &xAxisLabel, std::string &yAxisLabel)
PlusStatus ReadCalibrationResultsFromFile(const std::string &resultSaveFilename, TemporalCalibrationResult &calibResult)
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)
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 WriteCalibrationResultToFile(const std::string &outputFileName, const TemporalCalibrationResult &calibResult)
void SetMovingFrames(vtkIGSIOTrackedFrameList *frameList, FRAME_TYPE frameType)
void SetSamplingResolutionSec(double samplingResolutionSec)
void SetIntermediateFilesOutputDirectory(const std::string &outputDirectory)
static vtkIGSIOLogger * Instance()
Definition: ATC3DGm.h:319
PlusStatus GetMaxCalibrationError(double &maxCalibrationError)
PlusStatus GetFixedPositionSignal(vtkTable *fixedPositionSignal)
maxCalibrationError