Medical Imaging Interaction Toolkit  2016.11.0
Medical Imaging Interaction Toolkit
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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.