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