Medical Imaging Interaction Toolkit  2016.11.0
Medical Imaging Interaction Toolkit
mitkPlanarFigureReader.cpp
Go to the documentation of this file.
1 /*===================================================================
2 
3 The Medical Imaging Interaction Toolkit (MITK)
4 
5 Copyright (c) German Cancer Research Center,
6 Division of Medical and Biological Informatics.
7 All rights reserved.
8 
9 This software is distributed WITHOUT ANY WARRANTY; without
10 even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 A PARTICULAR PURPOSE.
12 
13 See LICENSE.txt or http://www.mitk.org for details.
14 
15 ===================================================================*/
16 
17 #include "mitkPlanarFigureReader.h"
18 
19 #include "mitkPlanarAngle.h"
20 #include "mitkPlanarArrow.h"
21 #include "mitkPlanarBezierCurve.h"
22 #include "mitkPlanarCircle.h"
23 #include "mitkPlanarCross.h"
25 #include "mitkPlanarEllipse.h"
27 #include "mitkPlanarLine.h"
28 #include "mitkPlanarPolygon.h"
29 #include "mitkPlanarRectangle.h"
31 #include "mitkPlaneGeometry.h"
32 
34 #include <mitkLocaleSwitch.h>
35 
36 #include <itksys/SystemTools.hxx>
37 #include <tinyxml.h>
38 
40  : PlanarFigureSource(), FileReader(), m_FileName(""), m_FilePrefix(""), m_FilePattern(""), m_Success(false)
41 {
42  this->SetNumberOfRequiredOutputs(1);
43  this->SetNumberOfIndexedOutputs(1);
44  this->SetNthOutput(0, this->MakeOutput(0));
45 
46  m_CanReadFromMemory = true;
47 
48  // this->Modified();
49  // this->GetOutput()->Modified();
50  // this->GetOutput()->ReleaseData();
51 }
52 
54 {
55 }
56 
58 {
59  mitk::LocaleSwitch localeSwitch("C");
60  m_Success = false;
61  this->SetNumberOfIndexedOutputs(0); // reset all outputs, we add new ones depending on the file content
62 
63  TiXmlDocument document;
64 
65  if (m_ReadFromMemory)
66  {
67  if (m_MemoryBuffer == nullptr || m_MemorySize == 0)
68  {
69  // check
70  itkWarningMacro(<< "Sorry, memory buffer has not been set!");
71  return;
72  }
73  if (m_MemoryBuffer[m_MemorySize - 1] == '\0')
74  {
75  document.Parse(m_MemoryBuffer);
76  }
77  else
78  {
79  auto tmpArray = new char[(int)m_MemorySize + 1];
80  tmpArray[m_MemorySize] = '\0';
81  memcpy(tmpArray, m_MemoryBuffer, m_MemorySize);
82 
83  document.Parse(m_MemoryBuffer);
84 
85  delete[] tmpArray;
86  }
87  }
88  else
89  {
90  if (m_FileName.empty())
91  {
92  itkWarningMacro(<< "Sorry, filename has not been set!");
93  return;
94  }
95  if (this->CanReadFile(m_FileName.c_str()) == false)
96  {
97  itkWarningMacro(<< "Sorry, can't read file " << m_FileName << "!");
98  return;
99  }
100  if (!document.LoadFile(m_FileName))
101  {
102  MITK_ERROR << "Could not open/read/parse " << m_FileName << ". TinyXML reports: '" << document.ErrorDesc()
103  << "'. "
104  << "The error occurred in row " << document.ErrorRow() << ", column " << document.ErrorCol() << ".";
105  return;
106  }
107  }
108 
109  int fileVersion = 1;
110  TiXmlElement *versionObject = document.FirstChildElement("Version");
111  if (versionObject != nullptr)
112  {
113  if (versionObject->QueryIntAttribute("FileVersion", &fileVersion) != TIXML_SUCCESS)
114  {
115  MITK_WARN << m_FileName << " does not contain version information! Trying version 1 format." << std::endl;
116  }
117  }
118  else
119  {
120  MITK_WARN << m_FileName << " does not contain version information! Trying version 1 format." << std::endl;
121  }
122  if (fileVersion !=
123  1) // add file version selection and version specific file parsing here, if newer file versions are created
124  {
125  MITK_WARN << "File version > 1 is not supported by this reader.";
126  return;
127  }
128 
129  /* file version 1 reader code */
130  for (TiXmlElement *pfElement = document.FirstChildElement("PlanarFigure"); pfElement != nullptr;
131  pfElement = pfElement->NextSiblingElement("PlanarFigure"))
132  {
133  if (pfElement == nullptr)
134  continue;
135 
136  std::string type = pfElement->Attribute("type");
137 
138  mitk::PlanarFigure::Pointer planarFigure = nullptr;
139  if (type == "PlanarAngle")
140  {
141  planarFigure = mitk::PlanarAngle::New();
142  }
143  else if (type == "PlanarCircle")
144  {
145  planarFigure = mitk::PlanarCircle::New();
146  }
147  else if (type == "PlanarEllipse")
148  {
149  planarFigure = mitk::PlanarEllipse::New();
150  }
151  else if (type == "PlanarCross")
152  {
153  planarFigure = mitk::PlanarCross::New();
154  }
155  else if (type == "PlanarFourPointAngle")
156  {
157  planarFigure = mitk::PlanarFourPointAngle::New();
158  }
159  else if (type == "PlanarLine")
160  {
161  planarFigure = mitk::PlanarLine::New();
162  }
163  else if (type == "PlanarPolygon")
164  {
165  planarFigure = mitk::PlanarPolygon::New();
166  }
167  else if (type == "PlanarSubdivisionPolygon")
168  {
169  planarFigure = mitk::PlanarSubdivisionPolygon::New();
170  }
171  else if (type == "PlanarRectangle")
172  {
173  planarFigure = mitk::PlanarRectangle::New();
174  }
175  else if (type == "PlanarArrow")
176  {
177  planarFigure = mitk::PlanarArrow::New();
178  }
179  else if (type == "PlanarDoubleEllipse")
180  {
181  planarFigure = mitk::PlanarDoubleEllipse::New();
182  }
183  else if (type == "PlanarBezierCurve")
184  {
185  planarFigure = mitk::PlanarBezierCurve::New();
186  }
187  else
188  {
189  // unknown type
190  MITK_WARN << "encountered unknown planar figure type '" << type << "'. Skipping this element.";
191  continue;
192  }
193 
194  // Read properties of the planar figure
195  for (TiXmlElement *propertyElement = pfElement->FirstChildElement("property"); propertyElement != nullptr;
196  propertyElement = propertyElement->NextSiblingElement("property"))
197  {
198  const char *keya = propertyElement->Attribute("key");
199  const std::string key(keya ? keya : "");
200 
201  const char *typea = propertyElement->Attribute("type");
202  const std::string type(typea ? typea : "");
203 
204  // hand propertyElement to specific reader
205  std::stringstream propertyDeserializerClassName;
206  propertyDeserializerClassName << type << "Serializer";
207 
208  const std::list<itk::LightObject::Pointer> readers =
209  itk::ObjectFactoryBase::CreateAllInstance(propertyDeserializerClassName.str().c_str());
210  if (readers.size() < 1)
211  {
212  MITK_ERROR << "No property reader found for " << type;
213  }
214  if (readers.size() > 1)
215  {
216  MITK_WARN << "Multiple property readers found for " << type << ". Using arbitrary first one.";
217  }
218 
219  for (auto iter = readers.cbegin(); iter != readers.cend(); ++iter)
220  {
221  if (BasePropertySerializer *reader = dynamic_cast<BasePropertySerializer *>(iter->GetPointer()))
222  {
223  const BaseProperty::Pointer property = reader->Deserialize(propertyElement->FirstChildElement());
224  if (property.IsNotNull())
225  {
226  planarFigure->GetPropertyList()->ReplaceProperty(key, property);
227  }
228  else
229  {
230  MITK_ERROR << "There were errors while loading property '" << key << "' of type " << type
231  << ". Your data may be corrupted";
232  }
233  break;
234  }
235  }
236  }
237 
238  // If we load a planarFigure, it has definitely been placed correctly.
239  // If we do not set this property here, we cannot load old planarFigures
240  // without messing up the interaction (PF-Interactor needs this property.
241  planarFigure->GetPropertyList()->SetBoolProperty("initiallyplaced", true);
242 
243  // Which features (length or circumference etc) a figure has is decided by whether it is closed or not
244  // the function SetClosed has to be called in case of PlanarPolygons to ensure they hold the correct feature
245  PlanarPolygon *planarPolygon = dynamic_cast<PlanarPolygon *>(planarFigure.GetPointer());
246  if (planarPolygon != nullptr)
247  {
248  bool isClosed = false;
249  planarFigure->GetPropertyList()->GetBoolProperty("closed", isClosed);
250  planarPolygon->SetClosed(isClosed);
251  }
252 
253  // Read geometry of containing plane
254  TiXmlElement *geoElement = pfElement->FirstChildElement("Geometry");
255  if (geoElement != nullptr)
256  {
257  try
258  {
259  // Create plane geometry
261 
262  // Extract and set plane transform parameters
263  const DoubleList transformList =
264  this->GetDoubleAttributeListFromXMLNode(geoElement->FirstChildElement("transformParam"), "param", 12);
265 
266  typedef mitk::BaseGeometry::TransformType TransformType;
267  TransformType::ParametersType parameters;
268  parameters.SetSize(12);
269 
270  unsigned int i;
271  DoubleList::const_iterator it;
272  for (it = transformList.cbegin(), i = 0; it != transformList.cend(); ++it, ++i)
273  {
274  parameters.SetElement(i, *it);
275  }
276 
277  typedef mitk::BaseGeometry::TransformType TransformType;
278  TransformType::Pointer affineGeometry = TransformType::New();
279  affineGeometry->SetParameters(parameters);
280  planeGeo->SetIndexToWorldTransform(affineGeometry);
281 
282  // Extract and set plane bounds
283  const DoubleList boundsList =
284  this->GetDoubleAttributeListFromXMLNode(geoElement->FirstChildElement("boundsParam"), "bound", 6);
285 
287 
288  BoundsArrayType bounds;
289  for (it = boundsList.cbegin(), i = 0; it != boundsList.cend(); ++it, ++i)
290  {
291  bounds[i] = *it;
292  }
293 
294  planeGeo->SetBounds(bounds);
295 
296  // Extract and set spacing and origin
297  const Vector3D spacing = this->GetVectorFromXMLNode(geoElement->FirstChildElement("Spacing"));
298  planeGeo->SetSpacing(spacing);
299 
300  const Point3D origin = this->GetPointFromXMLNode(geoElement->FirstChildElement("Origin"));
301  planeGeo->SetOrigin(origin);
302  planarFigure->SetPlaneGeometry(planeGeo);
303  }
304  catch (...)
305  {
306  }
307  }
308  TiXmlElement *cpElement = pfElement->FirstChildElement("ControlPoints");
309  bool first = true;
310  if (cpElement != nullptr)
311  for (TiXmlElement *vertElement = cpElement->FirstChildElement("Vertex"); vertElement != nullptr;
312  vertElement = vertElement->NextSiblingElement("Vertex"))
313  {
314  if (vertElement == nullptr)
315  continue;
316  int id = 0;
317  mitk::Point2D::ValueType x = 0.0;
318  mitk::Point2D::ValueType y = 0.0;
319  if (vertElement->QueryIntAttribute("id", &id) == TIXML_WRONG_TYPE)
320  return; // TODO: can we do a better error handling?
321  if (vertElement->QueryDoubleAttribute("x", &x) == TIXML_WRONG_TYPE)
322  return; // TODO: can we do a better error handling?
323  if (vertElement->QueryDoubleAttribute("y", &y) == TIXML_WRONG_TYPE)
324  return; // TODO: can we do a better error handling?
325  Point2D p;
326  p.SetElement(0, x);
327  p.SetElement(1, y);
328  if (first == true) // needed to set m_FigurePlaced to true
329  {
330  planarFigure->PlaceFigure(p);
331  first = false;
332  }
333  planarFigure->SetControlPoint(id, p, true);
334  }
335 
336  // Calculate feature quantities of this PlanarFigure
337  planarFigure->EvaluateFeatures();
338 
339  // Make sure that no control point is currently selected
340  planarFigure->DeselectControlPoint();
341 
342  // \TODO: what about m_FigurePlaced and m_SelectedControlPoint ??
343  this->SetNthOutput(this->GetNumberOfOutputs(), planarFigure); // add planarFigure as new output of this filter
344  }
345 
346  m_Success = true;
347 }
348 
350 {
351  if (e == nullptr)
352  throw std::invalid_argument("node invalid"); // TODO: can we do a better error handling?
353  mitk::Point3D point;
354  mitk::ScalarType p(-1.0);
355  if (e->QueryDoubleAttribute("x", &p) == TIXML_WRONG_TYPE)
356  throw std::invalid_argument("node malformatted"); // TODO: can we do a better error handling?
357  point.SetElement(0, p);
358  if (e->QueryDoubleAttribute("y", &p) == TIXML_WRONG_TYPE)
359  throw std::invalid_argument("node malformatted"); // TODO: can we do a better error handling?
360  point.SetElement(1, p);
361  if (e->QueryDoubleAttribute("z", &p) == TIXML_WRONG_TYPE)
362  throw std::invalid_argument("node malformatted"); // TODO: can we do a better error handling?
363  point.SetElement(2, p);
364  return point;
365 }
366 
368 {
369  if (e == nullptr)
370  throw std::invalid_argument("node invalid"); // TODO: can we do a better error handling?
371  mitk::Vector3D vector;
372  mitk::ScalarType p(-1.0);
373  if (e->QueryDoubleAttribute("x", &p) == TIXML_WRONG_TYPE)
374  throw std::invalid_argument("node malformatted"); // TODO: can we do a better error handling?
375  vector.SetElement(0, p);
376  if (e->QueryDoubleAttribute("y", &p) == TIXML_WRONG_TYPE)
377  throw std::invalid_argument("node malformatted"); // TODO: can we do a better error handling?
378  vector.SetElement(1, p);
379  if (e->QueryDoubleAttribute("z", &p) == TIXML_WRONG_TYPE)
380  throw std::invalid_argument("node malformatted"); // TODO: can we do a better error handling?
381  vector.SetElement(2, p);
382  return vector;
383 }
384 
386  TiXmlElement *e, const char *attributeNameBase, unsigned int count)
387 {
388  DoubleList list;
389 
390  if (e == nullptr)
391  throw std::invalid_argument("node invalid"); // TODO: can we do a better error handling?
392 
393  for (unsigned int i = 0; i < count; ++i)
394  {
395  mitk::ScalarType p(-1.0);
396  std::stringstream attributeName;
397  attributeName << attributeNameBase << i;
398 
399  if (e->QueryDoubleAttribute(attributeName.str().c_str(), &p) == TIXML_WRONG_TYPE)
400  throw std::invalid_argument("node malformatted"); // TODO: can we do a better error handling?
401  list.push_back(p);
402  }
403 
404  return list;
405 }
406 
408 {
409 }
410 
412 {
413  if (std::string(name).empty())
414  return false;
415 
416  return (itksys::SystemTools::LowerCase(itksys::SystemTools::GetFilenameLastExtension(name)) ==
417  ".pf"); // assume, we can read all .pf files
418 
419  // TiXmlDocument document(name);
420  // if (document.LoadFile() == false)
421  // return false;
422  // return (document.FirstChildElement("PlanarFigure") != NULL);
423 }
424 
425 bool mitk::PlanarFigureReader::CanReadFile(const std::string filename, const std::string, const std::string)
426 {
427  if (filename.empty())
428  return false;
429 
430  return (itksys::SystemTools::LowerCase(itksys::SystemTools::GetFilenameLastExtension(filename)) ==
431  ".pf"); // assume, we can read all .pf files
432 
433  // TiXmlDocument document(filename);
434  // if (document.LoadFile() == false)
435  // return false;
436  // return (document.FirstChildElement("PlanarFigure") != NULL);
437 }
438 
439 void mitk::PlanarFigureReader::ResizeOutputs(const unsigned int &num)
440 {
441  unsigned int prevNum = this->GetNumberOfOutputs();
442  this->SetNumberOfIndexedOutputs(num);
443  for (unsigned int i = prevNum; i < num; ++i)
444  {
445  this->SetNthOutput(i, this->MakeOutput(i).GetPointer());
446  }
447 }
std::list< double > DoubleList
itk::SmartPointer< Self > Pointer
mitk::Point3D GetPointFromXMLNode(TiXmlElement *e)
parses the element for the attributes x,y,z and returns a mitk::Point3D filled with these values ...
static Pointer New()
BoundingBoxType::BoundsArrayType BoundsArrayType
virtual void SetClosed(bool closed)
Set whether the polygon should be closed between first and last control point or not.
static Pointer New()
#define MITK_ERROR
Definition: mitkLogMacros.h:24
static Pointer New()
double ScalarType
GeometryTransformHolder::TransformType TransformType
virtual mitkBaseDataSourceGetOutputDeclarations itk::DataObject::Pointer MakeOutput(DataObjectPointerArraySizeType idx) override
static Pointer New()
DoubleList GetDoubleAttributeListFromXMLNode(TiXmlElement *e, const char *attributeNameBase, unsigned int count)
parses the element for the attributes name0 to nameN, where "name" and the number of attributes to re...
static Pointer New()
Implementation of PlanarFigure representing a polygon with two or more control points.
virtual void GenerateData() override
#define MITK_WARN
Definition: mitkLogMacros.h:23
Convenience class to temporarily change the current locale.
Base class for all filters which have an object of type mitk::PlanarFigure as output.
ValueType
Type of the value held by a Value object.
Definition: jsoncpp.h:345
static Pointer New()
static const std::string filename
mitk::Vector3D GetVectorFromXMLNode(TiXmlElement *e)
parses the element for the attributes x,y,z and returns a mitk::Vector3D filled with these values ...
Base class for objects that serialize BaseProperty types.
Interface class of readers that read from files.
static Pointer New()
virtual void ResizeOutputs(const unsigned int &num)
static Pointer New()
static bool CanReadFile(const std::string filename, const std::string filePrefix, const std::string filePattern)
static Pointer New()
virtual void GenerateOutputInformation() override
BoundingBoxType::BoundsArrayType BoundsArrayType
static itkEventMacro(BoundingShapeInteractionEvent, itk::AnyEvent) class MITKBOUNDINGSHAPE_EXPORT BoundingShapeInteractor Pointer New()
Basic interaction methods for mitk::GeometryData.