CustusX  15.8
An IGT application
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
cxWirePhantomWidget.cpp
Go to the documentation of this file.
1 /*=========================================================================
2 This file is part of CustusX, an Image Guided Therapy Application.
3 
4 Copyright (c) 2008-2014, SINTEF Department of Medical Technology
5 All rights reserved.
6 
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions are met:
9 
10 1. Redistributions of source code must retain the above copyright notice,
11  this list of conditions and the following disclaimer.
12 
13 2. Redistributions in binary form must reproduce the above copyright notice,
14  this list of conditions and the following disclaimer in the documentation
15  and/or other materials provided with the distribution.
16 
17 3. Neither the name of the copyright holder nor the names of its contributors
18  may be used to endorse or promote products derived from this software
19  without specific prior written permission.
20 
21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 =========================================================================*/
32 
33 #include "cxWirePhantomWidget.h"
34 
35 #include <QPushButton>
36 #include <QTextEdit>
37 #include <vtkPolyData.h>
38 #include <vtkPoints.h>
39 #include "cxMesh.h"
40 #include "cxDataLocations.h"
42 #include "cxPointMetric.h"
43 #include "cxDistanceMetric.h"
44 #include "cxTool.h"
45 #include "cxTypeConversions.h"
46 #include "cxPipelineWidget.h"
47 #include "cxDataReaderWriter.h"
48 #include "cxSmoothingImageFilter.h"
51 #include "cxSpaceProvider.h"
52 #include "cxReporter.h"
53 #include "cxRegistrationService.h"
55 #include "cxPatientModelService.h"
56 #include "cxViewGroupData.h"
57 
58 #include "cxProfile.h"
59 #include "cxViewService.h"
62 #include "cxTrackingService.h"
64 
65 namespace cx
66 {
67 
68 WirePhantomWidget::WirePhantomWidget(ctkPluginContext *pluginContext, QWidget* parent) :
69  RegistrationBaseWidget(RegServices(pluginContext), parent, "WirePhantomWidget", "Wire Phantom Test"),
70  mUsReconstructionService(new UsReconstructionServiceProxy(pluginContext))
71 {
72  mLastRegistration = Transform3D::Identity();
73 
74 
76 
77  // fill the pipeline with filters:
78  mPipeline.reset(new Pipeline(mServices.patientModelService));
79  XmlOptionFile options = profile()->getXmlSettings().descend("registration").descend("WirePhantomWidget");
80  FilterGroupPtr filters(new FilterGroup(options));
81  filters->append(FilterPtr(new SmoothingImageFilter(vs)));
82  filters->append(FilterPtr(new BinaryThresholdImageFilter(vs)));
83  filters->append(FilterPtr(new BinaryThinningImageFilter3DFilter(vs)));
84  mPipeline->initialize(filters);
85 
86  mPipeline->getNodes()[0]->setValueName("US Image:");
87  mPipeline->getNodes()[0]->setHelp("Select an US volume acquired from the wire phantom.");
88  mPipeline->setOption("Color", QVariant(QColor("red")));
89 
90  mLayout = new QVBoxLayout(this);
91 
92  mPipelineWidget = new PipelineWidget(mServices.visualizationService, mServices.patientModelService, NULL, mPipeline);
93 
94  QPushButton* showCrossButton = new QPushButton("Show Cross");
95  showCrossButton->setToolTip("Load the centerline description of the wire cross");
96  connect(showCrossButton, SIGNAL(clicked()), this, SLOT(loadNominalCross()));
97 
98  mMeasureButton = new QPushButton("Execute");
99  mMeasureButton->setToolTip("Measure deviation of input volume from nominal wire cross.");
100  connect(mMeasureButton, SIGNAL(clicked()), this, SLOT(measureSlot()));
101 
102  mCalibrationButton = new QPushButton("Probe calib");
103  mCalibrationButton->setToolTip(""
104  "Estimate probe calibration sMt\n"
105  "based on last accuracy test and\n"
106  "the current probe orientation");
107  connect(mCalibrationButton, SIGNAL(clicked()), this, SLOT(generate_sMt()));
108 
109  QLayout* buttonsLayout = new QHBoxLayout;
110  buttonsLayout->addWidget(mMeasureButton);
111  buttonsLayout->addWidget(mCalibrationButton);
112 
113  mResults = new QTextEdit;
114 
115  mLayout->addWidget(showCrossButton);
116  mLayout->addWidget(mPipelineWidget);
117  mLayout->addLayout(buttonsLayout);
118  mLayout->addWidget(mResults, 1);
119  mLayout->addStretch();
120 }
121 
123 {
124 }
125 
127 {
128  return "<html>"
129  "<h3>Measure US accuracy using the wire phantom.</h3>"
130  "<p>"
131  "Select a US recording of the wire phantom, then press"
132  "the register button to find deviation from the nominal"
133  "data. The output is given as a distance from measured"
134  "to nominal."
135  "</p>"
136  "</html>";
137 }
138 
139 MeshPtr WirePhantomWidget::loadNominalCross()
140 {
141  QString nominalCrossFilename = DataLocations::getRootConfigPath()+"/models/wire_phantom_cross_pts.vtk";
142  MeshPtr retval;
143 
144  std::map<QString, MeshPtr> meshes = mServices.patientModelService->getDataOfType<Mesh>();
145  for (std::map<QString, MeshPtr>::iterator iter=meshes.begin(); iter!=meshes.end(); ++iter)
146  if (iter->first.contains("wire_phantom_cross_pts"))
147  retval = iter->second;
148 
149  if (!retval)
150  {
151  QString infoText;
152  retval = boost::dynamic_pointer_cast<Mesh>(mServices.patientModelService->importData(nominalCrossFilename, infoText));
153  }
154 
155  if (!retval)
156  {
157  reportError(QString("failed to load %s.").arg(nominalCrossFilename));
158  }
159 
160  retval->setColor(QColor("green"));
161  this->showData(retval);
162 
163  return retval;
164 }
165 
166 void WirePhantomWidget::showData(DataPtr data)
167 {
168  mServices.visualizationService->getGroup(0)->addData(data->getUid());
169 }
170 
171 void WirePhantomWidget::measureSlot()
172 {
173  if (mPipeline->getPipelineTimedAlgorithm()->isRunning())
174  return;
175  connect(mPipeline->getPipelineTimedAlgorithm().get(), SIGNAL(finished()), this, SLOT(registration()));
176  mPipeline->execute();
177 }
178 
181 Vector3D WirePhantomWidget::findCentroid(MeshPtr mesh)
182 {
183  vtkPolyDataPtr poly = mesh->getVtkPolyData();
184  vtkPointsPtr points = poly->GetPoints();
185  int N = points->GetNumberOfPoints();
186  if (N==0)
187  return Vector3D(0,0,0);
188 
189  Vector3D acc(0,0,0);
190  for (int i = 0; i < N; ++i)
191  acc += Vector3D(points->GetPoint(i));
192  return acc/N;
193 }
194 
195 void WirePhantomWidget::registration()
196 {
197 // // disconnect the connection that started this registration
198  disconnect(mPipeline->getPipelineTimedAlgorithm().get(), SIGNAL(finished()), this, SLOT(registration()));
199 
200  // Verify that a centerline is available:
201  // - if no centerline, run centerline algo on segmentation,
202  // - if no segmentation, run segmentation
203  //
204  // Set centerline as Registration: moving data.
205  // Load nominal cross
206  // find center in nominal
207  // Execute V2V algo
208  // Find transform matrix fMm
209  // apply fMm to nominal, return diff
210  //
211 
212  DataPtr measuredCross = mPipeline->getNodes().back()->getData();
213  MeshPtr nominalCross = this->loadNominalCross();
214  if (!nominalCross || !measuredCross)
215  {
216  reportError("Missing fixed/moving data. WirePhantom measurement failed.");
217  return;
218  }
219 
220  mServices.registrationService->setFixedData(nominalCross);
221  mServices.registrationService->setMovingData(measuredCross);
222 
223  this->showData(nominalCross);
224  this->showData(measuredCross);
225 
226  SeansVesselReg vesselReg;
227  vesselReg.mt_auto_lts = true;
228  vesselReg.mt_ltsRatio = 80;
229  vesselReg.mt_doOnlyLinear = true;
230  QString logPath = mServices.patientModelService->getActivePatientFolder() + "/Logs/";
231 // vesselReg.setDebugOutput(true);
232 
233  bool success = vesselReg.execute(mServices.registrationService->getMovingData(), mServices.registrationService->getFixedData(), logPath);
234  if (!success)
235  {
236  reportWarning("Vessel registration failed.");
237  return;
238  }
239 
240  Transform3D linearTransform = vesselReg.getLinearResult();
241  std::cout << "v2v linear result:\n" << linearTransform << std::endl;
242 
243  vesselReg.checkQuality(linearTransform);
244  mLastRegistration = linearTransform;
245 
246  // The registration is performed in space r. Thus, given an old data position rMd, we find the
247  // new one as rM'd = Q * rMd, where Q is the inverted registration output.
248  // Delta is thus equal to Q:
249  Transform3D delta = linearTransform.inv();
250 // mRegistrationService->restart();
251  mServices.registrationService->setLastRegistrationTime(QDateTime::currentDateTime());//Instead of restart
252  mServices.registrationService->applyImage2ImageRegistration(delta, "Wire Phantom Measurement");
253 
254 
255  Vector3D t_delta = linearTransform.matrix().block<3, 1>(0, 3);
256  Eigen::AngleAxisd angleAxis = Eigen::AngleAxisd(linearTransform.matrix().block<3, 3>(0, 0));
257  double angle = angleAxis.angle();
258 
259  // Compute the centroid of the wire phantom.
260  // This should be the wire centre, given that the
261  // model is symmetrical.
262  Vector3D cross_r = this->findCentroid(nominalCross);
263  // Vector3D cross(134.25, 134.25, 99.50); // old hardcoded value: obsole after new measurements
264  // should be (CA20121022): <134.212 134.338 100.14>
265 // std::cout << "cross2 " << cross2 << std::endl;
266 
267 
268  // find transform to probe space t_us, i.e. the middle position from the us acquisition
269  std::pair<QString, Transform3D> probePos = this->getLastProbePosition();
270  Transform3D rMt_us = probePos.second;
271 
272  Vector3D cross_moving_r = linearTransform.coord(cross_r);
273  Vector3D diff_r = (cross_r - cross_moving_r);
274  Vector3D diff_tus = rMt_us.inv().vector(diff_r);
275 
276  struct Fmt
277  {
278  QString operator()(double val)
279  {
280  return QString("%1").arg(val, 2, 'f', 2);
281  }
282  };
283  Fmt fmt;
284 
285  QString result;
286  result += QString("Results for: %1\n").arg(mServices.registrationService->getMovingData()->getName());
287  result += QString("Shift vector (r): \t%1\t%2\t%3\n").arg(fmt(diff_r[0])).arg(fmt(diff_r[1])).arg(fmt(diff_r[2]));
288  if (!probePos.first.isEmpty())
289  result += QString("Shift vector (probe): \t%1\t%2\t%3\t(used tracking data from %4)\n").arg(fmt(diff_tus[0])).arg(fmt(diff_tus[1])).arg(fmt(diff_tus[2])).arg(probePos.first);
290  result += QString("Accuracy |v|: \t%1\tmm\n").arg(fmt(diff_r.length()));
291  result += QString("Angle: \t%1\t*\n").arg(fmt(angle / M_PI * 180.0));
292 
293  mResults->append(result);
294  report("Wire Phantom Test Results:\n"+result);
295 
296  this->showDataMetrics(cross_r);
297 }
298 
304 void WirePhantomWidget::showDataMetrics(Vector3D cross_r)
305 {
306  // add metrics displaying the distance from cross in the nominal and us spaces:
307  Transform3D usMnom = mServices.getSpaceProvider()->get_toMfrom(
308  mServices.getSpaceProvider()->getD(mServices.registrationService->getFixedData()),
309  mServices.getSpaceProvider()->getD(mServices.registrationService->getMovingData()));
310  Vector3D cross_us = usMnom.coord(cross_r);
311 
312  PointMetricPtr p1 = boost::dynamic_pointer_cast<PointMetric>(mServices.patientModelService->getData("cross_nominal"));
313  if (!p1)
314  p1 = mServices.patientModelService->createSpecificData<PointMetric>("cross_nominal");
315 // p1 = PointMetric::create("cross_nominal", "cross_nominal");
316  p1->get_rMd_History()->setParentSpace(mServices.registrationService->getFixedData()->getUid());
317  p1->setSpace(mServices.getSpaceProvider()->getD(mServices.registrationService->getFixedData()));
318  p1->setCoordinate(cross_r);
319 // dataManager()->loadData(p1);
320  mServices.patientModelService->insertData(p1);
321  //this->showData(p1);
322 
323  PointMetricPtr p2 = mServices.patientModelService->getData<PointMetric>("cross_us");
324  if (!p2)
325  p2 = mServices.patientModelService->createSpecificData<PointMetric>("cross_us");
326 // p2 = PointMetric::create("cross_us", "cross_us");
327  p2->get_rMd_History()->setParentSpace(mServices.registrationService->getMovingData()->getUid());
328  p2->setSpace(mServices.getSpaceProvider()->getD(mServices.registrationService->getMovingData()));
329  p2->setCoordinate(cross_us);
330 // dataManager()->loadData(p2);
331  mServices.patientModelService->insertData(p2);
332  //this->showData(p2);
333 
334  DistanceMetricPtr d0 = mServices.patientModelService->getData<DistanceMetric>("accuracy");
335  if (!d0)
336  d0 = mServices.patientModelService->createSpecificData<DistanceMetric>("accuracy");
337 // d0.reset(new DistanceMetric("accuracy", "accuracy"));
338  d0->get_rMd_History()->setParentSpace("reference");
339  d0->getArguments()->set(0, p1);
340  d0->getArguments()->set(1, p2);
341 // dataManager()->loadData(d0);
342  mServices.patientModelService->insertData(d0);
343  this->showData(d0);
344 }
345 
352 std::pair<QString, Transform3D> WirePhantomWidget::getLastProbePosition()
353 {
354  // find transform to probe space t_us, i.e. the middle position from the us acquisition
355  USReconstructInputData usData = mUsReconstructionService->getSelectedFileData();
356  Transform3D prMt_us = Transform3D::Identity();
357  if (usData.mPositions.empty())
358  return std::make_pair("", Transform3D::Identity());
359  prMt_us = usData.mPositions[usData.mPositions.size()/2].mPos;
360  return std::make_pair(usData.mFilename, prMt_us);
361 }
362 
363 void WirePhantomWidget::generate_sMt()
364 {
365  bool translateOnly = true;
366 
367 
368  std::pair<QString, Transform3D> probePos = this->getLastProbePosition();
369  Transform3D rMt_us = probePos.second;
370  if (probePos.first.isEmpty())
371  {
372  reportWarning("Cannot find probe position from last recording, aborting calibration test.");
373  return;
374  }
375 
376  USReconstructInputData usData = mUsReconstructionService->getSelectedFileData();
377  ToolPtr probe = mServices.trackingService->getTool(usData.mProbeUid);
378  if (!probe || !probe->hasType(Tool::TOOL_US_PROBE))
379  {
380  reportWarning("Cannot find probe, aborting calibration test.");
381  return;
382  }
383 
384 // ToolPtr probe = toolManager()->getDominantTool();
385 // if (!probe || !probe->getVisible() || !probe->hasType(Tool::TOOL_US_PROBE))
386 // {
387 // reportWarning("Cannot find visible probe, aborting calibration test.");
388 // return;
389 // }
390  if (!mServices.registrationService->getMovingData())
391  {
392  reportWarning("Cannot find moving data, aborting calibration test.");
393  return;
394  }
395 
396 
397  if (translateOnly)
398  {
399  Vector3D cross_r = this->findCentroid(this->loadNominalCross());
400  // Vector3D cross(134.25, 134.25, 99.50); // old hardcoded value: obsole after new measurements
401  // should be (CA20121022): <134.212 134.338 100.14>
402  // std::cout << "cross2 " << cross2 << std::endl;
403 
404 
405  // find transform to probe space t_us, i.e. the middle position from the us acquisition
406 // std::pair<QString, Transform3D> probePos = this->getLastProbePosition();
407 // Transform3D rMt_us = probePos.second;
408 
409  Vector3D cross_moving_r = mLastRegistration.coord(cross_r);
410  Vector3D diff_r = (cross_r - cross_moving_r);
411  Vector3D diff_tus = rMt_us.inv().vector(diff_r);
412 
413  Transform3D sMt = probe->getCalibration_sMt();
414  Transform3D sQt; // Q is the new calibration matrix.
415 
416  sQt = sMt * createTransformTranslate(diff_tus);
417 
418  report(QString(""
419  "Calculated new calibration matrix\n"
420  "adding only translation "
421  "from last accuracy test\n"
422  "and raw data %1:\n"
423  "%2").arg(probePos.first).arg(qstring_cast(sQt)));
424  }
425  else
426  {
427  Transform3D prMt = rMt_us;
428  Transform3D sMt = probe->getCalibration_sMt();
429  Transform3D prMs = prMt * sMt.inv();
430  Transform3D usMpr = mServices.registrationService->getMovingData()->get_rMd().inv() * mServices.patientModelService->get_rMpr();
431  Transform3D nomMus = mLastRegistration.inv();
432 
433  Transform3D sQt; // Q is the new calibration matrix.
434  Transform3D usMs = usMpr*prMs;
435 
436  // start with: nomMus * usMpr * prMs * sMt
437  // move usMpr*prMs to the left and collect a new sMt from the remains:
438  // start with: usMpr * prMs * (usMpr * prMs).inv() * nomMus * usMpr * prMs * sMt
439 
440  sQt = usMs.inv() * nomMus * usMs * sMt;
441 
442  report(QString(""
443  "Calculated new calibration matrix\n"
444  "from last accuracy test\n"
445  "and raw data %1.\n"
446  "Old calibration matrix sMt:\n"
447  "%2\n"
448  "New calibration matrix sQt:\n"
449  "%3\n").arg(probePos.first).arg(qstring_cast(sMt)).arg(qstring_cast(sQt)));
450  }
451 
452 }
453 
454 
455 //------------------------------------------------------------------------------
456 }//namespace cx
QString qstring_cast(const T &val)
cxResource_EXPORT ProfilePtr profile()
Definition: cxProfile.cpp:169
void reportError(QString msg)
Definition: cxLogger.cpp:92
SpaceProviderPtr getSpaceProvider()
A mesh data set.
Definition: cxMesh.h:61
boost::shared_ptr< class VisServices > VisServicesPtr
Definition: cxMainWindow.h:62
Transform3D Transform3D
Transform3D is a representation of an affine 3D transform.
vtkSmartPointer< class vtkPolyData > vtkPolyDataPtr
Definition: cxProbeSector.h:47
boost::shared_ptr< class Data > DataPtr
boost::shared_ptr< class Filter > FilterPtr
void reportWarning(QString msg)
Definition: cxLogger.cpp:91
boost::shared_ptr< FilterGroup > FilterGroupPtr
Definition: cxFilterGroup.h:86
WirePhantomWidget(ctkPluginContext *pluginContext, QWidget *parent=0)
Transform3D createTransformTranslate(const Vector3D &translation)
boost::shared_ptr< class DistanceMetric > DistanceMetricPtr
Eigen::Vector3d Vector3D
Vector3D is a representation of a point or vector in 3D.
Definition: cxVector3D.h:63
VisualizationServicePtr visualizationService
Definition: cxVisServices.h:60
static QString getRootConfigPath()
return path to root config folder. May be replaced with getExistingConfigPath()
void report(QString msg)
Definition: cxLogger.cpp:90
TrackingServicePtr trackingService
PatientModelServicePtr patientModelService
boost::shared_ptr< class Mesh > MeshPtr
Ultrasond probe. The tool has a Probe subinterface with a sector and a video stream.
Definition: cxTool.h:93
virtual QString defaultWhatsThis() const
Helper class for xml files used to store ssc/cx data.
#define M_PI
RegistrationServicePtr registrationService
Definition: cxRegServices.h:60
vtkSmartPointer< class vtkPoints > vtkPointsPtr
boost::shared_ptr< class Tool > ToolPtr
boost::shared_ptr< class PointMetric > PointMetricPtr