Medical Imaging Interaction Toolkit  2016.11.0
Medical Imaging Interaction Toolkit
mitkSceneIO.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 <Poco/Delegate.h>
18 #include <Poco/Path.h>
19 #include <Poco/TemporaryFile.h>
20 #include <Poco/Zip/Compress.h>
21 #include <Poco/Zip/Decompress.h>
22 
23 #include "mitkBaseDataSerializer.h"
25 #include "mitkSceneIO.h"
26 #include "mitkSceneReader.h"
27 
28 #include "mitkBaseRenderer.h"
29 #include "mitkProgressBar.h"
30 #include "mitkRenderingManager.h"
32 #include <mitkLocaleSwitch.h>
34 
35 #include <itkObjectFactoryBase.h>
36 
37 #include <tinyxml.h>
38 
39 #include <fstream>
40 #include <mitkIOUtil.h>
41 #include <sstream>
42 
43 #include "itksys/SystemTools.hxx"
44 
45 mitk::SceneIO::SceneIO() : m_WorkingDirectory(""), m_UnzipErrors(0)
46 {
47 }
48 
50 {
51 }
52 
54 {
55  mitk::UIDGenerator uidGen("UID_", 6);
56 
57  // std::string returnValue = mitk::StandardFileLocations::GetInstance()->GetOptionDirectory() +
58  // Poco::Path::separator() + "SceneIOTemp" + uidGen.GetUID();
59  std::string returnValue = Poco::Path::temp() + "SceneIOTemp" + uidGen.GetUID();
60  std::string uniquename = returnValue + Poco::Path::separator();
61  Poco::File tempdir(uniquename);
62 
63  try
64  {
65  bool existsNot = tempdir.createDirectory();
66  if (!existsNot)
67  {
68  MITK_ERROR << "Warning: Directory already exitsts: " << uniquename << " (choosing another)";
69  returnValue = mitk::StandardFileLocations::GetInstance()->GetOptionDirectory() + Poco::Path::separator() +
70  "SceneIOTempDirectory" + uidGen.GetUID();
71  uniquename = returnValue + Poco::Path::separator();
72  Poco::File tempdir2(uniquename);
73  if (!tempdir2.createDirectory())
74  {
75  MITK_ERROR << "Warning: Second directory also already exitsts: " << uniquename;
76  }
77  }
78  }
79  catch (std::exception &e)
80  {
81  MITK_ERROR << "Could not create temporary directory " << uniquename << ":" << e.what();
82  return "";
83  }
84 
85  return returnValue;
86 }
87 
89  DataStorage *pStorage,
90  bool clearStorageFirst)
91 {
92  mitk::LocaleSwitch localeSwitch("C");
93 
94  // prepare data storage
95  DataStorage::Pointer storage = pStorage;
96  if (storage.IsNull())
97  {
98  storage = StandaloneDataStorage::New().GetPointer();
99  }
100 
101  if (clearStorageFirst)
102  {
103  try
104  {
105  storage->Remove(storage->GetAll());
106  }
107  catch (...)
108  {
109  MITK_ERROR << "DataStorage cannot be cleared properly.";
110  }
111  }
112 
113  // test input filename
114  if (filename.empty())
115  {
116  MITK_ERROR << "No filename given. Not possible to load scene.";
117  return storage;
118  }
119 
120  // test if filename can be read
121  std::ifstream file(filename.c_str(), std::ios::binary);
122  if (!file.good())
123  {
124  MITK_ERROR << "Cannot open '" << filename << "' for reading";
125  return storage;
126  }
127 
128  // get new temporary directory
129  m_WorkingDirectory = CreateEmptyTempDirectory();
130  if (m_WorkingDirectory.empty())
131  {
132  MITK_ERROR << "Could not create temporary directory. Cannot open scene files.";
133  return storage;
134  }
135 
136  // unzip all filenames contents to temp dir
137  m_UnzipErrors = 0;
138  Poco::Zip::Decompress unzipper(file, Poco::Path(m_WorkingDirectory));
139  unzipper.EError += Poco::Delegate<SceneIO, std::pair<const Poco::Zip::ZipLocalFileHeader, const std::string>>(
140  this, &SceneIO::OnUnzipError);
141  unzipper.EOk += Poco::Delegate<SceneIO, std::pair<const Poco::Zip::ZipLocalFileHeader, const Poco::Path>>(
142  this, &SceneIO::OnUnzipOk);
143  unzipper.decompressAllFiles();
144  unzipper.EError -= Poco::Delegate<SceneIO, std::pair<const Poco::Zip::ZipLocalFileHeader, const std::string>>(
145  this, &SceneIO::OnUnzipError);
146  unzipper.EOk -= Poco::Delegate<SceneIO, std::pair<const Poco::Zip::ZipLocalFileHeader, const Poco::Path>>(
147  this, &SceneIO::OnUnzipOk);
148 
149  if (m_UnzipErrors)
150  {
151  MITK_ERROR << "There were " << m_UnzipErrors << " errors unzipping '" << filename
152  << "'. Will attempt to read whatever could be unzipped.";
153  }
154 
155  // test if index.xml exists
156  // parse index.xml with TinyXML
157  TiXmlDocument document(m_WorkingDirectory + mitk::IOUtil::GetDirectorySeparator() + "index.xml");
158  if (!document.LoadFile())
159  {
160  MITK_ERROR << "Could not open/read/parse " << m_WorkingDirectory << mitk::IOUtil::GetDirectorySeparator()
161  << "index.xml\nTinyXML reports: " << document.ErrorDesc() << std::endl;
162  return storage;
163  }
164 
166  if (!reader->LoadScene(document, m_WorkingDirectory, storage))
167  {
168  MITK_ERROR << "There were errors while loading scene file " << filename << ". Your data may be corrupted";
169  }
170 
171  // delete temp directory
172  try
173  {
174  Poco::File deleteDir(m_WorkingDirectory);
175  deleteDir.remove(true); // recursive
176  }
177  catch (...)
178  {
179  MITK_ERROR << "Could not delete temporary directory " << m_WorkingDirectory;
180  }
181 
182  // return new data storage, even if empty or uncomplete (return as much as possible but notify calling method)
183  return storage;
184 }
185 
187  const DataStorage *storage,
188  const std::string &filename)
189 {
190  if (!sceneNodes)
191  {
192  MITK_ERROR << "No set of nodes given. Not possible to save scene.";
193  return false;
194  }
195  if (!storage)
196  {
197  MITK_ERROR << "No data storage given. Not possible to save scene."; // \TODO: Technically, it would be possible to
198  // save the nodes without their relation
199  return false;
200  }
201 
202  if (filename.empty())
203  {
204  MITK_ERROR << "No filename given. Not possible to save scene.";
205  return false;
206  }
207 
208  mitk::LocaleSwitch localeSwitch("C");
209 
210  try
211  {
212  m_FailedNodes = DataStorage::SetOfObjects::New();
213  m_FailedProperties = PropertyList::New();
214 
215  // start XML DOM
216  TiXmlDocument document;
217  TiXmlDeclaration *decl = new TiXmlDeclaration(
218  "1.0",
219  "UTF-8",
220  ""); // TODO what to write here? encoding? standalone would mean that we provide a DTD somewhere...
221  document.LinkEndChild(decl);
222 
223  TiXmlElement *version = new TiXmlElement("Version");
224  version->SetAttribute("Writer", __FILE__);
225  version->SetAttribute("Revision", "$Revision: 17055 $");
226  version->SetAttribute("FileVersion", 1);
227  document.LinkEndChild(version);
228 
229  // DataStorage::SetOfObjects::ConstPointer sceneNodes = storage->GetSubset( predicate );
230 
231  if (sceneNodes.IsNull())
232  {
233  MITK_WARN << "Saving empty scene to " << filename;
234  }
235  else
236  {
237  if (sceneNodes->size() == 0)
238  {
239  MITK_WARN << "Saving empty scene to " << filename;
240  }
241 
242  MITK_INFO << "Storing scene with " << sceneNodes->size() << " objects to " << filename;
243 
244  m_WorkingDirectory = CreateEmptyTempDirectory();
245  if (m_WorkingDirectory.empty())
246  {
247  MITK_ERROR << "Could not create temporary directory. Cannot create scene files.";
248  return false;
249  }
250 
251  ProgressBar::GetInstance()->AddStepsToDo(sceneNodes->size());
252 
253  // find out about dependencies
254  typedef std::map<DataNode *, std::string> UIDMapType;
255  typedef std::map<DataNode *, std::list<std::string>> SourcesMapType;
256 
257  UIDMapType nodeUIDs; // for dependencies: ID of each node
258  SourcesMapType sourceUIDs; // for dependencies: IDs of a node's parent nodes
259 
260  UIDGenerator nodeUIDGen("OBJECT_");
261 
262  for (DataStorage::SetOfObjects::const_iterator iter = sceneNodes->begin(); iter != sceneNodes->end(); ++iter)
263  {
264  DataNode *node = iter->GetPointer();
265  if (!node)
266  continue; // unlikely event that we get a NULL pointer as an object for saving. just ignore
267 
268  // generate UIDs for all source objects
269  DataStorage::SetOfObjects::ConstPointer sourceObjects = storage->GetSources(node);
270  for (mitk::DataStorage::SetOfObjects::const_iterator sourceIter = sourceObjects->begin();
271  sourceIter != sourceObjects->end();
272  ++sourceIter)
273  {
274  if (std::find(sceneNodes->begin(), sceneNodes->end(), *sourceIter) == sceneNodes->end())
275  continue; // source is not saved, so don't generate a UID for this source
276 
277  // create a uid for the parent object
278  if (nodeUIDs[*sourceIter].empty())
279  {
280  nodeUIDs[*sourceIter] = nodeUIDGen.GetUID();
281  }
282 
283  // store this dependency for writing
284  sourceUIDs[node].push_back(nodeUIDs[*sourceIter]);
285  }
286 
287  if (nodeUIDs[node].empty())
288  {
289  nodeUIDs[node] = nodeUIDGen.GetUID();
290  }
291  }
292 
293  // write out objects, dependencies and properties
294  for (DataStorage::SetOfObjects::const_iterator iter = sceneNodes->begin(); iter != sceneNodes->end(); ++iter)
295  {
296  DataNode *node = iter->GetPointer();
297 
298  if (node)
299  {
300  TiXmlElement *nodeElement = new TiXmlElement("node");
301  std::string filenameHint(node->GetName());
302  filenameHint = itksys::SystemTools::MakeCindentifier(
303  filenameHint.c_str()); // escape filename <-- only allow [A-Za-z0-9_], replace everything else with _
304 
305  // store dependencies
306  UIDMapType::iterator searchUIDIter = nodeUIDs.find(node);
307  if (searchUIDIter != nodeUIDs.end())
308  {
309  // store this node's ID
310  nodeElement->SetAttribute("UID", searchUIDIter->second.c_str());
311  }
312 
313  SourcesMapType::iterator searchSourcesIter = sourceUIDs.find(node);
314  if (searchSourcesIter != sourceUIDs.end())
315  {
316  // store all source IDs
317  for (std::list<std::string>::iterator sourceUIDIter = searchSourcesIter->second.begin();
318  sourceUIDIter != searchSourcesIter->second.end();
319  ++sourceUIDIter)
320  {
321  TiXmlElement *uidElement = new TiXmlElement("source");
322  uidElement->SetAttribute("UID", sourceUIDIter->c_str());
323  nodeElement->LinkEndChild(uidElement);
324  }
325  }
326 
327  // store basedata
328  if (BaseData *data = node->GetData())
329  {
330  // std::string filenameHint( node->GetName() );
331  bool error(false);
332  TiXmlElement *dataElement(SaveBaseData(data, filenameHint, error)); // returns a reference to a file
333  if (error)
334  {
335  m_FailedNodes->push_back(node);
336  }
337 
338  // store basedata properties
339  PropertyList *propertyList = data->GetPropertyList();
340  if (propertyList && !propertyList->IsEmpty())
341  {
342  TiXmlElement *baseDataPropertiesElement(
343  SavePropertyList(propertyList, filenameHint + "-data")); // returns a reference to a file
344  dataElement->LinkEndChild(baseDataPropertiesElement);
345  }
346 
347  nodeElement->LinkEndChild(dataElement);
348  }
349 
350  // store all renderwindow specific propertylists
352  for (auto renderWindowName : propertyListKeys)
353  {
354  PropertyList *propertyList = node->GetPropertyList(renderWindowName);
355  if (propertyList && !propertyList->IsEmpty())
356  {
357  TiXmlElement *renderWindowPropertiesElement(
358  SavePropertyList(propertyList, filenameHint + "-" + renderWindowName)); // returns a reference to a file
359  renderWindowPropertiesElement->SetAttribute("renderwindow", renderWindowName);
360  nodeElement->LinkEndChild(renderWindowPropertiesElement);
361  }
362  }
363 
364  // don't forget the renderwindow independent list
365  PropertyList *propertyList = node->GetPropertyList();
366  if (propertyList && !propertyList->IsEmpty())
367  {
368  TiXmlElement *propertiesElement(
369  SavePropertyList(propertyList, filenameHint + "-node")); // returns a reference to a file
370  nodeElement->LinkEndChild(propertiesElement);
371  }
372  document.LinkEndChild(nodeElement);
373  }
374  else
375  {
376  MITK_WARN << "Ignoring NULL node during scene serialization.";
377  }
378 
380  } // end for all nodes
381  } // end if sceneNodes
382 
383  if (!document.SaveFile(m_WorkingDirectory + Poco::Path::separator() + "index.xml"))
384  {
385  MITK_ERROR << "Could not write scene to " << m_WorkingDirectory << Poco::Path::separator() << "index.xml"
386  << "\nTinyXML reports '" << document.ErrorDesc() << "'";
387  return false;
388  }
389  else
390  {
391  try
392  {
393  Poco::File deleteFile(filename.c_str());
394  if (deleteFile.exists())
395  {
396  deleteFile.remove();
397  }
398 
399  // create zip at filename
400  std::ofstream file(filename.c_str(), std::ios::binary | std::ios::out);
401  if (!file.good())
402  {
403  MITK_ERROR << "Could not open a zip file for writing: '" << filename << "'";
404  return false;
405  }
406  else
407  {
408  Poco::Zip::Compress zipper(file, true);
409  Poco::Path tmpdir(m_WorkingDirectory);
410  zipper.addRecursive(tmpdir);
411  zipper.close();
412  }
413  try
414  {
415  Poco::File deleteDir(m_WorkingDirectory);
416  deleteDir.remove(true); // recursive
417  }
418  catch (...)
419  {
420  MITK_ERROR << "Could not delete temporary directory " << m_WorkingDirectory;
421  return false; // ok?
422  }
423  }
424  catch (std::exception &e)
425  {
426  MITK_ERROR << "Could not create ZIP file from " << m_WorkingDirectory << "\nReason: " << e.what();
427  return false;
428  }
429  return true;
430  }
431  }
432  catch (std::exception &e)
433  {
434  MITK_ERROR << "Caught exception during saving temporary files to disk. Error description: '" << e.what() << "'";
435  return false;
436  }
437 }
438 
439 TiXmlElement *mitk::SceneIO::SaveBaseData(BaseData *data, const std::string &filenamehint, bool &error)
440 {
441  assert(data);
442  error = true;
443 
444  // find correct serializer
445  // the serializer must
446  // - create a file containing all information to recreate the BaseData object --> needs to know where to put this
447  // file (and a filename?)
448  // - TODO what to do about writers that creates one file per timestep?
449  TiXmlElement *element = new TiXmlElement("data");
450  element->SetAttribute("type", data->GetNameOfClass());
451 
452  // construct name of serializer class
453  std::string serializername(data->GetNameOfClass());
454  serializername += "Serializer";
455 
456  std::list<itk::LightObject::Pointer> thingsThatCanSerializeThis =
457  itk::ObjectFactoryBase::CreateAllInstance(serializername.c_str());
458  if (thingsThatCanSerializeThis.size() < 1)
459  {
460  MITK_ERROR << "No serializer found for " << data->GetNameOfClass() << ". Skipping object";
461  }
462 
463  for (std::list<itk::LightObject::Pointer>::iterator iter = thingsThatCanSerializeThis.begin();
464  iter != thingsThatCanSerializeThis.end();
465  ++iter)
466  {
467  if (BaseDataSerializer *serializer = dynamic_cast<BaseDataSerializer *>(iter->GetPointer()))
468  {
469  serializer->SetData(data);
470  serializer->SetFilenameHint(filenamehint);
471  serializer->SetWorkingDirectory(m_WorkingDirectory);
472  try
473  {
474  std::string writtenfilename = serializer->Serialize();
475  element->SetAttribute("file", writtenfilename);
476  error = false;
477  }
478  catch (std::exception &e)
479  {
480  MITK_ERROR << "Serializer " << serializer->GetNameOfClass() << " failed: " << e.what();
481  }
482  break;
483  }
484  }
485 
486  return element;
487 }
488 
489 TiXmlElement *mitk::SceneIO::SavePropertyList(PropertyList *propertyList, const std::string &filenamehint)
490 {
491  assert(propertyList);
492 
493  // - TODO what to do about shared properties (same object in two lists or behind several keys)?
494  TiXmlElement *element = new TiXmlElement("properties");
495 
496  // construct name of serializer class
498 
499  serializer->SetPropertyList(propertyList);
500  serializer->SetFilenameHint(filenamehint);
501  serializer->SetWorkingDirectory(m_WorkingDirectory);
502  try
503  {
504  std::string writtenfilename = serializer->Serialize();
505  element->SetAttribute("file", writtenfilename);
506  PropertyList::Pointer failedProperties = serializer->GetFailedProperties();
507  if (failedProperties.IsNotNull())
508  {
509  // move failed properties to global list
510  m_FailedProperties->ConcatenatePropertyList(failedProperties, true);
511  }
512  }
513  catch (std::exception &e)
514  {
515  MITK_ERROR << "Serializer " << serializer->GetNameOfClass() << " failed: " << e.what();
516  }
517 
518  return element;
519 }
520 
522 {
523  return m_FailedNodes.GetPointer();
524 }
525 
527 {
528  return m_FailedProperties;
529 }
530 
531 void mitk::SceneIO::OnUnzipError(const void * /*pSender*/,
532  std::pair<const Poco::Zip::ZipLocalFileHeader, const std::string> &info)
533 {
534  ++m_UnzipErrors;
535  MITK_ERROR << "Error while unzipping: " << info.second;
536 }
537 
538 void mitk::SceneIO::OnUnzipOk(const void * /*pSender*/,
539  std::pair<const Poco::Zip::ZipLocalFileHeader, const Poco::Path> & /*info*/)
540 {
541  // MITK_INFO << "Unzipped ok: " << info.second.toString();
542 }
void Progress(unsigned int steps=1)
Sets the current amount of progress to current progress + steps.
void OnUnzipError(const void *pSender, std::pair< const Poco::Zip::ZipLocalFileHeader, const std::string > &info)
mitk::PropertyList * GetPropertyList(const mitk::BaseRenderer *renderer=nullptr) const
Get the PropertyList of the renderer. If renderer is NULL, the BaseRenderer-independent PropertyList ...
bool IsEmpty() const
static char GetDirectorySeparator()
Definition: mitkIOUtil.cpp:363
Data management class that handles 'was created by' relations.
itk::SmartPointer< Self > Pointer
TiXmlElement * SaveBaseData(BaseData *data, const std::string &filenamehint, bool &error)
static Pointer New()
Generated unique IDs.
#define MITK_INFO
Definition: mitkLogMacros.h:22
std::vector< MapOfPropertyLists::key_type > PropertyListKeyNames
Definition: mitkDataNode.h:72
Base of all data objects.
Definition: mitkBaseData.h:39
#define MITK_ERROR
Definition: mitkLogMacros.h:24
virtual DataStorage::Pointer LoadScene(const std::string &filename, DataStorage *storage=NULL, bool clearStorageFirst=false)
Load a scene of objects from file.
Definition: mitkSceneIO.cpp:88
bool GetName(std::string &nodeName, const mitk::BaseRenderer *renderer=nullptr, const char *propertyKey="name") const
Convenience access method for accessing the name of an object (instance of StringProperty with proper...
Definition: mitkDataNode.h:366
Base class for objects that serialize BaseData types.
TiXmlElement * SavePropertyList(PropertyList *propertyList, const std::string &filenamehint)
static Pointer New()
BaseData * GetData() const
Get the data object (instance of BaseData, e.g., an Image) managed by this DataNode.
Key-value list holding instances of BaseProperty.
virtual bool SaveScene(DataStorage::SetOfObjects::ConstPointer sceneNodes, const DataStorage *storage, const std::string &filename)
Save a scene of objects to file.
static ProgressBar * GetInstance()
static method to get the GUI dependent ProgressBar-instance so the methods for steps to do and progre...
static void info(const char *fmt,...)
Definition: svm.cpp:100
itk::SmartPointer< const Self > ConstPointer
virtual SetOfObjects::ConstPointer GetSources(const mitk::DataNode *node, const NodePredicateBase *condition=nullptr, bool onlyDirectSources=true) const =0
returns a set of source objects for a given node that meet the given condition(s).
PropertyListKeyNames GetPropertyListNames() const
The "names" used for (renderer-specific) PropertyLists in GetPropertyList(string).
const FailedBaseDataListType * GetFailedNodes()
Get a list of nodes (BaseData containers) that failed to be read/written.
const PropertyList * GetFailedProperties()
Get a list of properties that failed to be read/written.
virtual ~SceneIO()
Definition: mitkSceneIO.cpp:49
std::string GetOptionDirectory()
Return directory of/for option files.
#define MITK_WARN
Definition: mitkLogMacros.h:23
Convenience class to temporarily change the current locale.
static const std::string filename
void AddStepsToDo(unsigned int steps)
Adds steps to totalSteps.
DataStorage::SetOfObjects FailedBaseDataListType
Definition: mitkSceneIO.h:40
void OnUnzipOk(const void *pSender, std::pair< const Poco::Zip::ZipLocalFileHeader, const Poco::Path > &info)
static StandardFileLocations * GetInstance()
std::string CreateEmptyTempDirectory()
Definition: mitkSceneIO.cpp:53
Class for nodes of the DataTree.
Definition: mitkDataNode.h:66
static itkEventMacro(BoundingShapeInteractionEvent, itk::AnyEvent) class MITKBOUNDINGSHAPE_EXPORT BoundingShapeInteractor Pointer New()
Basic interaction methods for mitk::GeometryData.