PlusLib  2.9.0
Software library for tracked ultrasound image acquisition, calibration, and processing.
vtkStylusCalibrationTest.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 
13 #include "PlusConfigure.h"
14 #include "PlusMath.h"
15 #include "igsioTrackedFrame.h"
16 #include "vtkPlusDataCollector.h"
17 #include "vtkMatrix4x4.h"
18 #include "vtkMinimalStandardRandomSequence.h"
20 #include "vtkPlusChannel.h"
21 #include "vtkPlusDevice.h"
22 #include "vtkSmartPointer.h"
23 #include "vtkIGSIOTrackedFrameList.h"
24 #include "vtkTransform.h"
25 #include "vtkIGSIOTransformRepository.h"
26 #include "vtkXMLDataElement.h"
27 #include "vtkXMLUtilities.h"
28 #include "vtksys/CommandLineArguments.hxx"
29 #include "vtksys/SystemTools.hxx"
30 #include <iostream>
31 #include <stdlib.h>
32 
34 const double TRANSLATION_ERROR_THRESHOLD = 0.5; // error threshold is 0.5mm
35 const double ROTATION_ERROR_THRESHOLD = 0.5; // error threshold is 0.5deg
36 
37 int CompareCalibrationResultsWithBaseline(const char* baselineFileName, const char* currentResultFileName, const char* stylusCoordinateFrame, const char* stylusTipCoordinateFrame);
38 
39 int main(int argc, char* argv[])
40 {
41  std::string inputConfigFileName;
42  std::string inputBaselineFileName;
43 
44  int numberOfPointsToAcquire = 100;
45  int verboseLevel = vtkPlusLogger::LOG_LEVEL_UNDEFINED;
46  double outlierGenerationProbability = 0.0;
47 
48  vtksys::CommandLineArguments cmdargs;
49  cmdargs.Initialize(argc, argv);
50 
51  cmdargs.AddArgument("--config-file", vtksys::CommandLineArguments::EQUAL_ARGUMENT, &inputConfigFileName, "Configuration file name");
52  cmdargs.AddArgument("--baseline-file", vtksys::CommandLineArguments::EQUAL_ARGUMENT, &inputBaselineFileName, "Name of file storing baseline calibration results");
53  cmdargs.AddArgument("--number-of-points-to-acquire", vtksys::CommandLineArguments::EQUAL_ARGUMENT, &numberOfPointsToAcquire, "Number of acquired points during the pivot calibration (default: 100)");
54  cmdargs.AddArgument("--outlier-generation-probability", vtksys::CommandLineArguments::EQUAL_ARGUMENT, &outlierGenerationProbability, "Probability for a point being an outlier. If this number is larger than 0 then some valid measurement points are replaced by randomly generated samples to test the robustness of the algorithm. (range: 0.0-1.0; default: 0.0)");
55  cmdargs.AddArgument("--verbose", vtksys::CommandLineArguments::EQUAL_ARGUMENT, &verboseLevel, "Verbose level (1=error only, 2=warning, 3=info, 4=debug, 5=trace)");
56 
57  if (!cmdargs.Parse())
58  {
59  std::cerr << "Problem parsing arguments" << std::endl;
60  std::cout << "Help: " << cmdargs.GetHelp() << std::endl;
61  exit(EXIT_FAILURE);
62  }
63 
64  vtkPlusLogger::Instance()->SetLogLevel(verboseLevel);
65 
66  std::string programPath("./"), errorMsg;
67  if (!vtksys::SystemTools::FindProgramPath(argv[0], programPath, errorMsg))
68  {
69  LOG_ERROR(errorMsg);
70  }
71  programPath = vtksys::SystemTools::GetParentDirectory(programPath.c_str());
72 
73  LOG_INFO("Initialize");
74 
75  // Read configuration
76  vtkSmartPointer<vtkXMLDataElement> configRootElement = vtkSmartPointer<vtkXMLDataElement>::New();
77  if (PlusXmlUtils::ReadDeviceSetConfigurationFromFile(configRootElement, inputConfigFileName.c_str()) == PLUS_FAIL)
78  {
79  LOG_ERROR("Unable to read configuration from file " << inputConfigFileName.c_str());
80  return EXIT_FAILURE;
81  }
82 
84 
85  // Initialize data collection
86  vtkSmartPointer<vtkPlusDataCollector> dataCollector = vtkSmartPointer<vtkPlusDataCollector>::New();
87  if (dataCollector->ReadConfiguration(configRootElement) != PLUS_SUCCESS)
88  {
89  LOG_ERROR("Unable to parse configuration from file " << inputConfigFileName.c_str());
90  exit(EXIT_FAILURE);
91  }
92 
93  if (dataCollector->Connect() != PLUS_SUCCESS)
94  {
95  LOG_ERROR("Unable to initialize data collection!");
96  exit(EXIT_FAILURE);
97  }
98  if (dataCollector->Start() != PLUS_SUCCESS)
99  {
100  LOG_ERROR("Unable to start data collection!");
101  exit(EXIT_FAILURE);
102  }
103  vtkPlusChannel* aChannel(NULL);
104  vtkPlusDevice* aDevice(NULL);
105  if (dataCollector->GetDevice(aDevice, std::string("TrackerDevice")) != PLUS_SUCCESS)
106  {
107  LOG_ERROR("Unable to locate device by ID: \'TrackerDevice\'");
108  exit(EXIT_FAILURE);
109  }
110  if (aDevice->GetOutputChannelByName(aChannel, "TrackerStream") != PLUS_SUCCESS)
111  {
112  LOG_ERROR("Unable to locate channel by ID: \'TrackerStream\'");
113  exit(EXIT_FAILURE);
114  }
115  if (aChannel->GetTrackingDataAvailable() == false)
116  {
117  LOG_ERROR("Channel \'" << aChannel->GetChannelId() << "\' is not tracking!");
118  exit(EXIT_FAILURE);
119  }
120 
121  // Initialize stylus calibration
122  vtkSmartPointer<vtkPlusPivotCalibrationAlgo> pivotCalibration = vtkSmartPointer<vtkPlusPivotCalibrationAlgo>::New();
123  if (pivotCalibration == NULL)
124  {
125  LOG_ERROR("Unable to instantiate pivot calibration algorithm class!");
126  exit(EXIT_FAILURE);
127  }
128 
129  if (pivotCalibration->ReadConfiguration(configRootElement) != PLUS_SUCCESS)
130  {
131  LOG_ERROR("Unable to read pivot calibration configuration!");
132  exit(EXIT_FAILURE);
133  }
134 
135  // Create and initialize transform repository
136  igsioTrackedFrame trackedFrame;
137  aChannel->GetTrackedFrame(trackedFrame);
138 
139  vtkSmartPointer<vtkIGSIOTransformRepository> transformRepository = vtkSmartPointer<vtkIGSIOTransformRepository>::New();
140  transformRepository->SetTransforms(trackedFrame);
141 
142  // Check stylus tool
143  igsioTransformName stylusToReferenceTransformName(pivotCalibration->GetObjectMarkerCoordinateFrame(), pivotCalibration->GetReferenceCoordinateFrame());
144 
145  vtkSmartPointer<vtkMinimalStandardRandomSequence> random = vtkSmartPointer<vtkMinimalStandardRandomSequence>::New();
146  random->SetSeed(183495439); // Just some random number was chosen as seed
147  int numberOfOutliers = 0;
148 
149  // Acquire positions for pivot calibration
150  for (int i = 0; i < numberOfPointsToAcquire; ++i)
151  {
152  vtksys::SystemTools::Delay(50);
153  vtkPlusLogger::PrintProgressbar((100.0 * i) / numberOfPointsToAcquire);
154 
155  vtkSmartPointer<vtkMatrix4x4> stylusToReferenceMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
156 
157 
158  igsioTrackedFrame trackedFrame;
159  if (aChannel->GetTrackedFrame(trackedFrame) != PLUS_SUCCESS)
160  {
161  LOG_ERROR("Failed to get tracked frame!");
162  exit(EXIT_FAILURE);
163  }
164 
165  if (transformRepository->SetTransforms(trackedFrame) != PLUS_SUCCESS)
166  {
167  LOG_ERROR("Failed to update transforms in repository with tracked frame!");
168  exit(EXIT_FAILURE);
169  }
170 
171  ToolStatus status(TOOL_INVALID);
172  if (transformRepository->GetTransform(stylusToReferenceTransformName, stylusToReferenceMatrix, &status) != PLUS_SUCCESS || status != TOOL_OK)
173  {
174  LOG_ERROR("No valid transform found between stylus to reference!");
175  exit(EXIT_FAILURE);
176  }
177 
178  // Generate an outlier point to see if the calibration algorithm is robust enough
179  random->Next();
180  if (random->GetValue() < outlierGenerationProbability)
181  {
182  numberOfOutliers++;
183  random->Next();
184  double rotX = random->GetRangeValue(-160, 160);
185  random->Next();
186  double rotY = random->GetRangeValue(-160, 160);
187  random->Next();
188  double rotZ = random->GetRangeValue(-160, 160);
189  double translation[3] = {0};
190  random->Next();
191  translation[0] = random->GetRangeValue(-1000, 1000);
192  random->Next();
193  translation[1] = random->GetRangeValue(-1000, 1000);
194  random->Next();
195  translation[2] = random->GetRangeValue(-1000, 1000);
196  // Random matrix that perturbs the actual perfect transform
197  vtkSmartPointer<vtkTransform> randomStylusToReferenceTransform = vtkSmartPointer<vtkTransform>::New();
198  randomStylusToReferenceTransform->Identity();
199  randomStylusToReferenceTransform->Translate(translation);
200  randomStylusToReferenceTransform->RotateX(rotX);
201  randomStylusToReferenceTransform->RotateY(rotY);
202  randomStylusToReferenceTransform->RotateZ(rotZ);
203  stylusToReferenceMatrix->DeepCopy(randomStylusToReferenceTransform->GetMatrix());
204  }
205 
206  pivotCalibration->InsertNextCalibrationPoint(stylusToReferenceMatrix);
207  }
208  vtkPlusLogger::PrintProgressbar(100.0);
209 
210  if (numberOfOutliers > 0)
211  {
212  LOG_INFO("Number of generated outlier samples: " << numberOfOutliers);
213  }
214 
215  if (pivotCalibration->DoPivotCalibration(transformRepository) != PLUS_SUCCESS)
216  {
217  LOG_ERROR("Calibration error!");
218  exit(EXIT_FAILURE);
219  }
220 
221  LOG_INFO("Number of detected outliers: " << pivotCalibration->GetNumberOfDetectedOutliers());
222  LOG_INFO("Mean calibration error: " << pivotCalibration->GetPivotCalibrationErrorMm() << " mm");
223 
224  // Save result
225  if (transformRepository->WriteConfiguration(configRootElement) != PLUS_SUCCESS)
226  {
227  LOG_ERROR("Failed to write pivot calibration result to configuration element!");
228  exit(EXIT_FAILURE);
229  }
230 
231  std::string calibrationResultFileName = "StylusCalibrationTest.xml";
232  LOG_INFO("Writing calibration result (" << pivotCalibration->GetObjectPivotPointCoordinateFrame() << " to " << pivotCalibration->GetObjectMarkerCoordinateFrame() << " transform) to " << calibrationResultFileName);
233  vtksys::SystemTools::RemoveFile(calibrationResultFileName.c_str());
234  igsioCommon::XML::PrintXML(calibrationResultFileName.c_str(), configRootElement);
235 
236  if (!inputBaselineFileName.empty())
237  {
238  // Compare to baseline
239  std::string objectMarkerCoordinateFrame = pivotCalibration->GetObjectMarkerCoordinateFrame();
240  std::string objectPivotCoordinateFrame = pivotCalibration->GetObjectPivotPointCoordinateFrame();
241  if (CompareCalibrationResultsWithBaseline(inputBaselineFileName.c_str(), calibrationResultFileName.c_str(), objectMarkerCoordinateFrame.c_str(), objectPivotCoordinateFrame.c_str()) != 0)
242  {
243  LOG_ERROR("Comparison of calibration data to baseline failed");
244  std::cout << "Exit failure!!!" << std::endl;
245  return EXIT_FAILURE;
246  }
247  }
248  else
249  {
250  LOG_DEBUG("Baseline file is not specified. Computed results are not compared to baseline results.");
251  }
252 
253  std::cout << "Exit success!!!" << std::endl;
254  return EXIT_SUCCESS;
255 }
256 
257 //-----------------------------------------------------------------------------
258 
259 // return the number of differences
260 int CompareCalibrationResultsWithBaseline(const char* baselineFileName, const char* currentResultFileName, const char* stylusCoordinateFrame, const char* stylusTipCoordinateFrame)
261 {
262  int numberOfFailures = 0;
263 
264  // Load current stylus calibration
265  vtkSmartPointer<vtkXMLDataElement> currentRootElem = vtkSmartPointer<vtkXMLDataElement>::Take(vtkXMLUtilities::ReadElementFromFile(currentResultFileName));
266  if (currentRootElem == NULL)
267  {
268  LOG_ERROR("Unable to read the current configuration file: " << currentResultFileName);
269  return ++numberOfFailures;
270  }
271 
272  vtkSmartPointer<vtkMatrix4x4> stylusTipToStylusTransformCurrent = vtkSmartPointer<vtkMatrix4x4>::New();
273  double currentError(0);
274  if (vtkPlusConfig::GetInstance()->ReadTransformToCoordinateDefinition(currentRootElem, stylusTipCoordinateFrame, stylusCoordinateFrame, stylusTipToStylusTransformCurrent, &currentError) != PLUS_SUCCESS)
275  {
276  LOG_ERROR("Failed to read current pivot calibration result from configuration file!");
277  return ++numberOfFailures;
278  }
279 
280  // Load baseline stylus calibration
281  vtkSmartPointer<vtkXMLDataElement> baselineRootElem = vtkSmartPointer<vtkXMLDataElement>::Take(vtkXMLUtilities::ReadElementFromFile(baselineFileName));
282  if (baselineRootElem == NULL)
283  {
284  LOG_ERROR("Unable to read the baseline configuration file: " << baselineFileName);
285  return ++numberOfFailures;
286  }
287 
288  vtkSmartPointer<vtkMatrix4x4> stylusTipToStylusTransformBaseline = vtkSmartPointer<vtkMatrix4x4>::New();
289  double baselineError(0);
290  if (vtkPlusConfig::GetInstance()->ReadTransformToCoordinateDefinition(baselineRootElem, stylusTipCoordinateFrame, stylusCoordinateFrame, stylusTipToStylusTransformBaseline, &baselineError) != PLUS_SUCCESS)
291  {
292  LOG_ERROR("Failed to read current stylus calibration result from configuration file!");
293  return ++numberOfFailures;
294  }
295 
296  // Compare the transforms
297  double posDiff = igsioMath::GetPositionDifference(stylusTipToStylusTransformCurrent, stylusTipToStylusTransformBaseline);
298  double rotDiff = igsioMath::GetOrientationDifference(stylusTipToStylusTransformCurrent, stylusTipToStylusTransformBaseline);
299 
300  std::ostringstream currentTransform;
301  stylusTipToStylusTransformCurrent->PrintSelf(currentTransform, vtkIndent(0));
302  std::ostringstream baselineTransform;
303  stylusTipToStylusTransformBaseline->PrintSelf(baselineTransform, vtkIndent(0));
304 
305  if (posDiff > TRANSLATION_ERROR_THRESHOLD)
306  {
307  LOG_ERROR("Transform mismatch: translation difference is " << posDiff << ", maximum allowed is " << TRANSLATION_ERROR_THRESHOLD);
308  LOG_INFO("Current StylusTipToStylus transform: " << currentTransform.str());
309  LOG_INFO("Baseline StylusTipToStylus transform: " << baselineTransform.str());
310  numberOfFailures++;
311  }
312 
313  if (rotDiff > ROTATION_ERROR_THRESHOLD)
314  {
315  LOG_ERROR("Transform mismatch: rotation difference is " << rotDiff << ", maximum allowed is " << TRANSLATION_ERROR_THRESHOLD);
316  LOG_INFO("Current transform: " << currentTransform.str());
317  LOG_INFO("Baseline transform: " << baselineTransform.str());
318  numberOfFailures++;
319  }
320 
321  return numberOfFailures;
322 }
Abstract interface for tracker and video devices.
Definition: vtkPlusDevice.h:60
virtual PlusStatus GetTrackedFrame(double timestamp, igsioTrackedFrame &trackedFrame, bool enableImageData=true)
bool GetTrackingDataAvailable()
for i
int main(int argc, char *argv[])
const double ROTATION_ERROR_THRESHOLD
#define PLUS_FAIL
Definition: PlusCommon.h:43
static vtkPlusConfig * GetInstance()
PlusStatus GetOutputChannelByName(vtkPlusChannel *&aChannel, const char *aChannelId)
const double TRANSLATION_ERROR_THRESHOLD
#define PLUS_SUCCESS
Definition: PlusCommon.h:44
int CompareCalibrationResultsWithBaseline(const char *baselineFileName, const char *currentResultFileName, const char *stylusCoordinateFrame, const char *stylusTipCoordinateFrame)
virtual char * GetChannelId()
static vtkIGSIOLogger * Instance()
Contains an optional timestamped circular buffer containing the video images and a number of timestam...
void SetDeviceSetConfigurationData(vtkXMLDataElement *deviceSetConfigurationData)
static PlusStatus ReadDeviceSetConfigurationFromFile(vtkXMLDataElement *config, const char *filename)
Definition: PlusXmlUtils.h:23