CustusX  15.8
An IGT application
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
cxElastixExecuter.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 
34 #include "cxElastixExecuter.h"
35 
36 #include <QProcess>
37 #include <QFile>
38 #include <QDir>
39 
40 #include "cxTypeConversions.h"
41 #include "cxTime.h"
42 #include "cxData.h"
43 #include "cxBoundingBox3D.h"
44 #include "cxTransformFile.h"
45 #include "cxCustomMetaImage.h"
46 
47 #include "cxPatientModelService.h"
48 
49 namespace cx
50 {
51 
52 ElastixExecuter::ElastixExecuter(RegServices services, QObject* parent) :
53  TimedBaseAlgorithm("ElastiX", 1000),
54  mServices(services)
55 {
56  mProcess = new QProcess(this);
57  connect(mProcess, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(processStateChanged(QProcess::ProcessState)));
58  connect(mProcess, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError)));
59  connect(mProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(processFinished(int, QProcess::ExitStatus)));
60 }
61 
63 {
64  disconnect(mProcess, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError)));
65  mProcess->close();
66 }
67 
69 {
70  disconnect(mProcess, SIGNAL(readyRead()), this, SLOT(processReadyRead()));
71 
72  if (on)
73  {
74  connect(mProcess, SIGNAL(readyRead()), this, SLOT(processReadyRead()));
75  // mProcess->setProcessChannelMode(QProcess::ForwardedChannels);
76  mProcess->setProcessChannelMode(QProcess::MergedChannels);
77  mProcess->setReadChannel(QProcess::StandardOutput);
78  }
79 }
80 
81 
82 bool ElastixExecuter::setInput(QString application,
83  DataPtr fixed,
84  DataPtr moving,
85  QString outdir,
86  QStringList parameterfiles)
87 {
88  mFixed = fixed;
89  mMoving = moving;
90 
91  if (!fixed || !moving)
92  {
93  reportWarning("Failed to start elastiX registration, fixed or missing image missing.");
94  return false;
95  }
96 
97  if (mProcess->state() != QProcess::NotRunning)
98  {
99  reportWarning("Failed to start elastiX registration, process already running");
100  return false;
101  }
102 
103  // create output dir (required by ElastiX)
104  QDir().mkpath(outdir);
105 
106  QString initFilename = this->writeInitTransformToElastixfile(fixed, moving, outdir);
107  this->writeInitTransformToCalfile(fixed, moving, outdir);
108 
109  mLastOutdir = outdir;
110 
111  QStringList cmd;
112  cmd << "\"" + application + "\"";
113  cmd << "-f" << mServices.patientModelService->getActivePatientFolder()+"/"+fixed->getFilename();
114  cmd << "-m" << mServices.patientModelService->getActivePatientFolder()+"/"+moving->getFilename();
115  cmd << "-out" << outdir;
116  cmd << "-t0" << initFilename;
117  for (int i=0; i<parameterfiles.size(); ++i)
118  cmd << "-p" << parameterfiles[i];
119 
120  QString commandLine = cmd.join(" ");
121  report(QString("Executing registration with command line: [%1]").arg(commandLine));
122 
123  mProcess->start(commandLine);
124  return true;
125 }
126 
128 {
129  emit aboutToStart(); // if properly set: triggers the setInput()
130 }
131 
133 {
134  std::cout << "TODO: check ElastixExecuter::isFinished()" << std::endl;
135  return mProcess->atEnd();
136 }
137 
139 {
140  return mProcess->state()!=QProcess::NotRunning;
141 }
142 
143 QString ElastixExecuter::writeInitTransformToElastixfile(
144  DataPtr fixed,
145  DataPtr moving,
146  QString outdir)
147 {
148  // elastiX uses the transforms present in the mhd files. If the mhd info is up to date
149  // with the dataManager info, the T0=I, i.e we dont need to tell elastiX anything.
150  // If NOT up to date, then compare file and dataManager, then insert the difference as T0:
151  //
152  // Let f be fixed, m be moving, mm and ff be the intermediate spaces between the file and r:
153  // rMd is the transform stored in ssc.
154  // ddMd is the transform stored in the mhd file.
155  // T0 is the remainder to be sent to elastiX
156  //
157  // mMf = mMr*rMf = mMmm*mmMr*rMff*ffMf = mMmm*T0*ffMf
158  // -->
159  // T0 = mmMm*mMr*rMf*fMff
160  //
161  Transform3D rMf = fixed->get_rMd();
162  Transform3D rMm = moving->get_rMd();
163  Transform3D ffMf = this->getFileTransform_ddMd(mFixed);
164  Transform3D mmMm = this->getFileTransform_ddMd(mMoving);
165 // Transform3D mMf = rMm.inv() * rMf;
166  // -->
167  // The remainder transform, not stored in mhd files, must be sent to elastiX:
168  Transform3D T0 = mmMm*rMm.inv()*rMf*ffMf.inv();
169 
170 // Transform3D mMf = moving->get_rMd().inv() * fixed->get_rMd();
171  ElastixEulerTransform E = ElastixEulerTransform::create(T0, fixed->boundingBox().center());
172 
173  QString elastiXText = QString(""
174  "// Input transform file\n"
175  "// Auto-generated by CustusX on %1\n"
176  "\n"
177  "(Transform \"EulerTransform\")\n"
178  "(NumberOfParameters 6)\n"
179  "(TransformParameters %2 %3)\n"
180  "(InitialTransformParametersFileName \"NoInitialTransform\")\n"
181  "(HowToCombineTransforms \"Compose\")\n"
182  "\n"
183  "// EulerTransform specific\n"
184  "(CenterOfRotationPoint %4)\n"
185  "(ComputeZYX \"false\")\n"
186  "").arg(QDateTime::currentDateTime().toString(timestampSecondsFormatNice()),
190 
191  QFile initTransformFile(outdir+"/t0.txt");
192  if (!initTransformFile.open(QIODevice::WriteOnly | QIODevice::Text))
193  {
194  reportWarning(QString("Failed to open file %1 for writing.").arg(initTransformFile.fileName()));
195  }
196  initTransformFile.write(elastiXText.toLatin1());
197  return initTransformFile.fileName();
198 }
199 
200 QString ElastixExecuter::writeInitTransformToCalfile(
201  DataPtr fixed,
202  DataPtr moving,
203  QString outdir)
204 {
205  Transform3D mMf = moving->get_rMd().inv() * fixed->get_rMd();
206 
207  TransformFile file(outdir+"/moving_M_fixed_initial.cal");
208  file.write(mMf);
209 
210  return file.fileName();
211 }
212 
213 void ElastixExecuter::processReadyRead()
214 {
215  report(QString(mProcess->readAllStandardOutput()));
216 }
217 
218 void ElastixExecuter::processError(QProcess::ProcessError error)
219 {
220  QString msg;
221  msg += "Registration process reported an error: ";
222 
223  switch (error)
224  {
225  case QProcess::FailedToStart:
226  msg += "Failed to start";
227  break;
228  case QProcess::Crashed:
229  msg += "Crashed";
230  break;
231  case QProcess::Timedout:
232  msg += "Timed out";
233  break;
234  case QProcess::WriteError:
235  msg += "Write Error";
236  break;
237  case QProcess::ReadError:
238  msg += "Read Error";
239  break;
240  case QProcess::UnknownError:
241  msg += "Unknown Error";
242  break;
243  default:
244  msg += "Invalid error";
245  }
246 
247  reportError(msg);
248 }
249 
250 void ElastixExecuter::processFinished(int code, QProcess::ExitStatus status)
251 {
252  if (status == QProcess::CrashExit)
253  reportError("Registration process crashed");
254 }
255 
256 
257 void ElastixExecuter::processStateChanged(QProcess::ProcessState newState)
258 {
259 // removed text: to much information
260 
261  QString msg;
262  msg += "Registration process";
263 
264  if (newState == QProcess::Running)
265  {
266 // report(msg + " running.");
267  emit started(0);
268  }
269  if (newState == QProcess::NotRunning)
270  {
271  emit finished();
272 // report(msg + " not running.");
273  }
274  if (newState == QProcess::Starting)
275  {
276 // report(msg + " starting.");
277  }
278 }
279 
280 QString ElastixExecuter::findMostRecentTransformOutputFile() const
281 {
282  QString retval;
283  for (int i=0; ; ++i)
284  {
285  QString filename = QString(mLastOutdir + "/TransformParameters.%1.txt").arg(i);
286  if (!QFileInfo(filename).exists())
287  break;
288  retval = filename;
289  }
290  return retval;
291 }
292 
293 Transform3D ElastixExecuter::getFileTransform_ddMd(DataPtr volume)
294 {
295  QString patFolder = mServices.patientModelService->getActivePatientFolder();
296  CustomMetaImagePtr reader = CustomMetaImage::create(patFolder+"/"+volume->getFilename());
297  Transform3D ddMd = reader->readTransform();
298  return ddMd;
299 }
300 
302 {
303  Transform3D mmMff = this->getAffineResult_mmMff(ok);
304  Transform3D ffMf = this->getFileTransform_ddMd(mFixed);
305  Transform3D mmMm = this->getFileTransform_ddMd(mMoving);
306 
307  return mmMm.inv() * mmMff * ffMf;
308 }
309 
310 Transform3D ElastixExecuter::getAffineResult_mmMff(bool* ok)
311 {
312  QString filename = this->findMostRecentTransformOutputFile();
313  Transform3D mMf = Transform3D::Identity();
314 
315  if (filename.isEmpty())
316  {
317  if (ok)
318  *ok = false;
319 
320  TransformFile file(mLastOutdir+"/moving_M_fixed_registered.cal");
321  mMf = file.read(ok);
322 
323  if (ok && !*ok)
324  {
325  reportWarning("Failed to read registration results.");
326  }
327 
328  return mMf;
329  }
330 
331  while (true)
332  {
333  ElastixParameterFile file(filename);
334 
335  bool useDirectionCosines = file.readParameterBool("UseDirectionCosines");
336  if (useDirectionCosines)
337  {
338  reportWarning("Elastix UseDirectionCosines is not supported. Result is probably wrong.");
339  }
340 
341  QString transformType = file.readParameterString("Transform");
342  if (transformType=="EulerTransform")
343  {
344  if (ok)
345  *ok = true;
346  Transform3D mQf = file.readEulerTransform();
347  // concatenate transforms:
348  mMf = mQf * mMf;
349  }
350  else if (transformType=="AffineTransform")
351  {
352  if (ok)
353  *ok = true;
354  Transform3D mQf = file.readAffineTransform();
355  // concatenate transforms:
356  mMf = mQf * mMf;
357  }
358  else
359  {
360  // accept invalid transforms, but emit warning.
361 // if (ok)
362 // *ok = false;
363  reportWarning(QString("TransformType [%1] is not supported by CustusX. Registration result from %2 ignored.").arg(transformType).arg(filename));
364  }
365 
366  filename = file.readParameterString("InitialTransformParametersFileName");
367  if (filename.isEmpty() || filename=="NoInitialTransform")
368  break;
369  }
370 
371  return mMf;
372 }
373 
374 
376 {
377  if (ok)
378  *ok = false;
379 
380  QString retval;
381  int i=0;
382  for (; ; ++i)
383  {
384  //TODO only mhd supported
385  QString filename = QString(mLastOutdir + "/result.%1.mhd").arg(i);
386  if (!QFileInfo(filename).exists())
387  break;
388  retval = filename;
389  }
390 
391  if (retval.isEmpty())
392  return retval;
393 
394  QString paramFilename = QString(mLastOutdir + "/TransformParameters.%1.txt").arg(i-1);
395  ElastixParameterFile file(paramFilename);
396  QString transform = file.readParameterString("Transform");
397 
398  if ((transform=="BSplineTransform") || (transform=="SplineKernelTransform"))
399  {
400  report(QString("Reading result file %1 created with transform %2").arg(retval).arg(transform));
401  if (ok)
402  *ok = true;
403  return retval;
404  }
405  else
406  {
407  return "";
408  }
409 
410 }
411 
412 
413 // --------------------------------------------------------
414 // --------------------------------------------------------
415 // --------------------------------------------------------
416 
417 
418 
419 
421 {
422  QString transformType = this->readParameterString("Transform");
423  if (transformType!="EulerTransform")
424  reportError("Assert failure: attempting to read EulerTransform");
425 
426  int numberOfParameters = this->readParameterInt("NumberOfParameters");
427  if (numberOfParameters!=6)
428  {
429  reportWarning(QString("Expected 6 Euler parameters, got %1").arg(numberOfParameters));
430  return Transform3D::Identity();
431  }
432  std::vector<double> tp = this->readParameterDoubleVector("TransformParameters");
433  std::vector<double> cor = this->readParameterDoubleVector("CenterOfRotationPoint");
434 
436  Vector3D(tp[0], tp[1], tp[2]),
437  Vector3D(tp[3], tp[4], tp[5]),
438  Vector3D(cor[0], cor[1], cor[2]));
439 
440  return E.toMatrix();
441 }
442 
444 {
445  QString transformType = this->readParameterString("Transform");
446  if (transformType!="AffineTransform")
447  reportError("Assert failure: attempting to read AffineTransform");
448 
449  int numberOfParameters = this->readParameterInt("NumberOfParameters");
450  if (numberOfParameters!=12)
451  {
452  reportWarning(QString("Expected 12 Euler parameters, got %1").arg(numberOfParameters));
453  return Transform3D::Identity();
454  }
455  std::vector<double> tp = this->readParameterDoubleVector("TransformParameters");
456 // std::vector<double> cor = this->readParameterDoubleVector("CenterOfRotationPoint");
457 
458  Transform3D M = Transform3D::Identity();
459 
460  for (int r=0; r<3; ++r)
461  for (int c=0; c<3; ++c)
462  M(r,c) = tp[3*r+c];
463  for (int r=0; r<3; ++r)
464  M(r,3) = tp[9+r];
465 
466  return M;
467 }
468 
469 ElastixParameterFile::ElastixParameterFile(QString filename) : mFile(filename)
470 {
471  if (!mFile.open(QIODevice::ReadOnly | QIODevice::Text))
472  {
473  reportWarning(QString("Can't open ElastiX result file %1").arg(filename));
474  }
475  mText = QString(mFile.readAll());
476 // std::cout << "Loaded text from " << filename << ":\n" << mText << std::endl;
477 }
478 
479 QString ElastixParameterFile::readParameterRawValue(QString key)
480 {
481  QStringList lines = mText.split('\n');
482 
483  QString regexpStr = QString(""
484  "\\s*" // match zero or more whitespace
485  "\\(" // match beginning (
486  "\\s*" // match zero or more whitespace
487  "%1" // key
488  "\\s+" // match one or more whitespace
489  "(" // start grab value
490  "[^\\)]*" // value - anything up to the closing )
491  ")" // end grab value
492  "\\)" // match ending )
493  "[^\\n]*" // rest of line - ignore
494  ).arg(key);
495  QRegExp rx(regexpStr);
496 // std::cout << regexpStr << std::endl;
497 
498  lines.indexOf(rx);
499 
500 // if (lines.indexOf(rx)>=0)
501 // {
502 // std::cout << "FOUND0 " << rx.cap(0) << std::endl;
503 // std::cout << "FOUND1 " << rx.cap(1) << std::endl;
504 // }
505 
506  return rx.cap(1);
507 }
508 
510 {
511  QString retval = this->readParameterRawValue(key);
512  if (retval.startsWith("\"") && retval.endsWith("\""))
513  {
514  retval = retval.replace(0, 1, "");
515  retval = retval.replace(retval.size()-1, 1, "");
516  }
517 
518  return retval;
519 }
520 
522 {
523  QString text = this->readParameterString(key);
524  return (text=="true") ? true : false;
525 }
526 
528 {
529  QString retval = this->readParameterRawValue(key);
530  return retval.toInt();
531 }
532 
533 std::vector<double> ElastixParameterFile::readParameterDoubleVector(QString key)
534 {
535  QString retval = this->readParameterRawValue(key);
536  return convertQString2DoubleVector(retval);
537 }
538 
539 } /* namespace cx */
QString qstring_cast(const T &val)
ElastixParameterFile(QString filename)
bool setInput(QString application, DataPtr fixed, DataPtr moving, QString outdir, QStringList parameterfiles)
DoubleBoundingBox3D transform(const Transform3D &m, const DoubleBoundingBox3D &bb)
void finished()
should be emitted when at the end of postProcessingSlot
QString readParameterString(QString key)
void reportError(QString msg)
Definition: cxLogger.cpp:92
File format for storing a 4x4 matrix.The read/write methods emit error messages if you dont use the o...
Base class for algorithms that wants to time their execution.
virtual bool isFinished() const
static ElastixEulerTransform create(Vector3D angles_xyz, Vector3D translation, Vector3D centerOfRotation)
int readParameterInt(QString key)
Transform3D Transform3D
Transform3D is a representation of an affine 3D transform.
QString timestampSecondsFormatNice()
Definition: cxTime.cpp:47
Transform3D toMatrix() const
QString getNonlinearResultVolume(bool *ok=0)
boost::shared_ptr< class Data > DataPtr
boost::shared_ptr< class CustomMetaImage > CustomMetaImagePtr
void reportWarning(QString msg)
Definition: cxLogger.cpp:91
bool readParameterBool(QString key)
Eigen::Vector3d Vector3D
Vector3D is a representation of a point or vector in 3D.
Definition: cxVector3D.h:63
ElastixExecuter(RegServices services, QObject *parent=NULL)
void report(QString msg)
Definition: cxLogger.cpp:90
virtual bool isRunning() const
std::vector< double > readParameterDoubleVector(QString key)
void aboutToStart()
emitted at start of execute. Use to perform preprocessing
PatientModelServicePtr patientModelService
void setDisplayProcessMessages(bool on)
std::vector< double > convertQString2DoubleVector(const QString &input, bool *ok)
Transform3D getAffineResult_mMf(bool *ok=0)
static CustomMetaImagePtr create(QString filename)
void started(int maxSteps)
emitted at start of run.