fw4spl
SReader.cpp
1 /* ***** BEGIN LICENSE BLOCK *****
2  * FW4SPL - Copyright (C) IRCAD, 2009-2018.
3  * Distributed under the terms of the GNU Lesser General Public License (LGPL) as
4  * published by the Free Software Foundation.
5  * ****** END LICENSE BLOCK ****** */
6 
7 #include "ioAtoms/SReader.hpp"
8 
9 #include <fwAtomConversion/convert.hpp>
10 
11 #include <fwAtomsBoostIO/Reader.hpp>
12 #include <fwAtomsBoostIO/types.hpp>
13 
14 #include <fwAtomsFilter/factory/new.hpp>
15 #include <fwAtomsFilter/IFilter.hpp>
16 
17 #include <fwAtomsPatch/PatchingManager.hpp>
18 
19 #include <fwCom/Signal.hxx>
20 
21 #include <fwData/Array.hpp>
22 #include <fwData/Composite.hpp>
23 #include <fwData/location/Folder.hpp>
24 #include <fwData/location/SingleFile.hpp>
25 
26 #include <fwDataTools/helper/Composite.hpp>
27 
28 #include <fwGui/Cursor.hpp>
29 #include <fwGui/dialog/LocationDialog.hpp>
30 #include <fwGui/dialog/MessageDialog.hpp>
31 
32 #include <fwJobs/Aggregator.hpp>
33 #include <fwJobs/Job.hpp>
34 
35 #include <fwMDSemanticPatch/PatchLoader.hpp>
36 
37 #include <fwServices/macros.hpp>
38 
39 #include <fwZip/ReadDirArchive.hpp>
40 #include <fwZip/ReadZipArchive.hpp>
41 
42 #include <boost/algorithm/string/join.hpp>
43 #include <boost/assign/list_of.hpp>
44 #include <boost/filesystem/path.hpp>
45 
46 namespace ioAtoms
47 {
48 
49 fwServicesRegisterMacro( ::fwIO::IReader, ::ioAtoms::SReader, ::fwData::Object );
50 
51 static const ::fwCom::Signals::SignalKeyType JOB_CREATED_SIGNAL = "jobCreated";
52 
54  = ::boost::assign::map_list_of(".xml", "XML")
55  (".xmlz", "Zipped XML")
56  (".json", "JSON")
57  (".jsonz", "Zipped JSON");
58 
59 //-----------------------------------------------------------------------------
60 
62  m_outputMode(false),
63  m_uuidPolicy("Change"),
64  m_useAtomsPatcher(false),
65  m_context("Undefined"),
66  m_version("Undefined"),
67  m_filter("")
68 {
69  m_sigJobCreated = newSignal< JobCreatedSignalType >( JOB_CREATED_SIGNAL );
70 
71  for(SReader::FileExtension2NameType::value_type ext : s_EXTENSIONS)
72  {
73  m_allowedExts.insert(m_allowedExts.end(), ext.first);
74  }
75 }
76 
77 //-----------------------------------------------------------------------------
78 
80 {
81 }
82 
83 //-----------------------------------------------------------------------------
84 
86 {
87  if (m_outputMode)
88  {
89  this->setOutput(::fwIO::s_DATA_KEY, nullptr);
90  }
91 }
92 
93 //-----------------------------------------------------------------------------
94 
96 {
98 
99  m_customExts.clear();
100  m_allowedExtLabels.clear();
101 
102  const ConfigType config = this->getConfigTree();
103 
104  const auto archiveCfgs = config.equal_range("archive");
105 
106  for (auto it = archiveCfgs.first; it != archiveCfgs.second; ++it)
107  {
108  const std::string backend = it->second.get<std::string>("<xmlattr>.backend");
109  SLM_ASSERT("No backend attribute given in archive tag", backend != "");
110  SLM_ASSERT("Unsupported backend '" + backend + "'", s_EXTENSIONS.find("." + backend) != s_EXTENSIONS.end());
111 
112  const auto extCfgs = it->second.equal_range("extension");
113 
114  for (auto itExt = extCfgs.first; itExt != extCfgs.second; ++itExt)
115  {
116  const std::string extension = itExt->second.get<std::string>("");
117  SLM_ASSERT("No extension given for backend '" + backend + "'", !extension.empty());
118  SLM_ASSERT("Extension must begin with '.'", extension[0] == '.');
119 
120  m_customExts[extension] = backend;
121  m_allowedExtLabels[extension] = itExt->second.get("<xmlattr>.label", "");
122  }
123  }
124 
125  const auto extensionsCfg = config.get_child_optional("extensions");
126 
127  if (extensionsCfg)
128  {
129  m_allowedExts.clear();
130 
131  const auto extCfgs = extensionsCfg->equal_range("extension");
132  for (auto it = extCfgs.first; it != extCfgs.second; ++it)
133  {
134  const std::string ext = it->second.get<std::string>("");
135 
136  // The extension must be found either in custom extensions list or in known extensions
137  FileExtension2NameType::const_iterator itKnown = s_EXTENSIONS.find(ext);
138  FileExtension2NameType::const_iterator itCustom = m_customExts.find(ext);
139 
140  const bool extIsKnown = (itKnown != SReader::s_EXTENSIONS.end() || itCustom != m_customExts.end());
141  SLM_ASSERT("Extension '" + ext + "' is not allowed in configuration", extIsKnown);
142 
143  if(extIsKnown)
144  {
145  m_allowedExts.insert(m_allowedExts.end(), ext);
146  m_allowedExtLabels[ext] = it->second.get("<xmlattr>.label", "");
147  }
148  }
149  }
150  else
151  {
152  m_allowedExts.clear();
153 
154  for(FileExtension2NameType::value_type ext : m_customExts)
155  {
156  m_allowedExts.insert(m_allowedExts.end(), ext.first);
157  }
158 
159  for(SReader::FileExtension2NameType::value_type ext : SReader::s_EXTENSIONS)
160  {
161  m_allowedExts.insert(m_allowedExts.end(), ext.first);
162  m_allowedExtLabels[ext.first] = ext.second;
163  }
164  }
165 
166  m_filter = config.get("filter", "");
167  m_uuidPolicy = config.get("uuidPolicy", m_uuidPolicy);
168 
169  SLM_ASSERT("Unknown policy : '"
170  + m_uuidPolicy +
171  "', available policies : 'Strict','Change' or 'Reuse'.",
172  "Strict" == m_uuidPolicy || "Change" == m_uuidPolicy || "Reuse" == m_uuidPolicy );
173 
174  const auto patcherCfg = config.get_child_optional("patcher");
175 
176  if (patcherCfg)
177  {
178  m_context = patcherCfg->get<std::string>("<xmlattr>.context", "MedicalData");
179  m_version = patcherCfg->get<std::string>("<xmlattr>.version",
181  m_useAtomsPatcher = true;
182  }
183 
184  const std::string output = config.get<std::string>("out.<xmlattr>.key", "");
185  if (output == ::fwIO::s_DATA_KEY )
186  {
187  m_outputMode = true;
188  }
189 
190  SLM_ASSERT("'Reuse' policy is only available when data is set as 'out'", m_outputMode || "Reuse" != m_uuidPolicy);
191 }
192 
193 //-----------------------------------------------------------------------------
194 
196 {
197  if(this->hasLocationDefined())
198  {
199  ::fwData::Object::sptr data = this->getInOut< ::fwData::Object >(::fwIO::s_DATA_KEY);
200  if (!m_outputMode && !data)
201  {
202  FW_DEPRECATED_KEY(::fwIO::s_DATA_KEY, "inout", "18.0");
203  data = this->getObject< ::fwData::Object >();
204  }
205 
206  ::fwGui::Cursor cursor;
207  cursor.setCursor(::fwGui::ICursor::BUSY);
208 
209  try
210  {
211  const ::boost::filesystem::path& filePath = this->getFile();
212  const ::boost::filesystem::path folderPath = filePath.parent_path();
213  const ::boost::filesystem::path filename = filePath.filename();
214  std::string extension = ::boost::filesystem::extension(filePath);
215 
216  FW_RAISE_IF( "Unable to guess file format (missing extension)", extension.empty() );
217 
218  if(m_customExts.find(extension) != m_customExts.end())
219  {
220  extension = "." + m_customExts[extension];
221  }
222 
223  ::fwAtoms::Object::sptr atom;
224 
225  const unsigned int progressBarOffset = 10;
226 
227  // Reading file : job 1
228  ::fwJobs::Job::sptr fileReadingJob = ::fwJobs::Job::New("Reading " + extension + " file",
229  [ =, &atom](::fwJobs::Job& runningJob)
230  {
231  runningJob.doneWork(progressBarOffset);
232 
233  // Read atom
234  ::fwZip::IReadArchive::sptr readArchive;
235  ::boost::filesystem::path archiveRootName;
236  ::fwAtomsBoostIO::FormatType format = ::fwAtomsBoostIO::UNSPECIFIED;
237 
238  if ( extension == ".json" )
239  {
240  readArchive = ::fwZip::ReadDirArchive::New(folderPath.string());
241  archiveRootName = filename;
242  format = ::fwAtomsBoostIO::JSON;
243  }
244  else if ( extension == ".jsonz" )
245  {
246  readArchive = ::fwZip::ReadZipArchive::New(filePath.string());
247  archiveRootName = "root.json";
248  format = ::fwAtomsBoostIO::JSON;
249  }
250  else if ( extension == ".xml" )
251  {
252  readArchive = ::fwZip::ReadDirArchive::New(folderPath.string());
253  archiveRootName = filename;
254  format = ::fwAtomsBoostIO::XML;
255  }
256  else if ( extension == ".xmlz" )
257  {
258  readArchive = ::fwZip::ReadZipArchive::New(filePath.string());
259  archiveRootName = "root.xml";
260  format = ::fwAtomsBoostIO::XML;
261  }
262  else
263  {
264  FW_RAISE( "This file extension '" << extension << "' is not managed" );
265  }
266 
268  atom = ::fwAtoms::Object::dynamicCast( reader.read( readArchive, archiveRootName, format ) );
269 
270  FW_RAISE_IF( "Invalid atoms file :'" << filePath << "'", !atom );
271 
272  runningJob.doneWork(progressBarOffset);
273 
274  runningJob.done();
275 
276  }, m_associatedWorker);
277 
278  // patching atom : job 2
279  ::fwJobs::Job::sptr patchingJob = ::fwJobs::Job::New("Reading " + extension + " file",
280  [ =, &atom](::fwJobs::Job& runningJob)
281  {
282  if(runningJob.cancelRequested())
283  {
284  return;
285  }
286 
287  runningJob.doneWork(progressBarOffset);
288 
290  if ( m_useAtomsPatcher )
291  {
292  FW_RAISE_IF( "Unable to load data, found '" << atom->getMetaInfo("context")
293  << "' context, but '" << m_context <<
294  "' was excepted.",
295  atom->getMetaInfo("context") != m_context);
296 
297  ::fwAtomsPatch::PatchingManager globalPatcher(atom);
298  atom = globalPatcher.transformTo( m_version );
299  }
300 
301  if(!m_filter.empty())
302  {
303  ::fwAtomsFilter::IFilter::sptr filter = ::fwAtomsFilter::factory::New(m_filter);
304  OSLM_ASSERT("Failed to create IFilter implementation '" << m_filter << "'", filter);
305  filter->apply(atom);
306  }
307  runningJob.done();
308  }, m_associatedWorker);
309 
310  ::fwData::Object::sptr newData;
311 
312  // convert to fwData : job 3
313  ::fwJobs::Job::sptr atomToDataJob = ::fwJobs::Job::New("Reading " + extension + " file",
314  [ =, &newData, &atom](::fwJobs::Job& runningJob)
315  {
316  runningJob.doneWork(progressBarOffset);
317  if(runningJob.cancelRequested())
318  {
319  return;
320  }
321  if("Strict" == m_uuidPolicy)
322  {
323  newData = ::fwAtomConversion::convert(atom, ::fwAtomConversion::AtomVisitor::StrictPolicy());
324  }
325  else if("Reuse" == m_uuidPolicy)
326  {
327  newData = ::fwAtomConversion::convert(atom, ::fwAtomConversion::AtomVisitor::ReusePolicy());
328  }
329  else
330  {
331  newData = ::fwAtomConversion::convert(atom, ::fwAtomConversion::AtomVisitor::ChangePolicy());
332  }
333 
334  runningJob.done();
335  }, m_associatedWorker);
336 
337  ::fwJobs::Aggregator::sptr jobs = ::fwJobs::Aggregator::New(extension + " reader");
338  jobs->add(fileReadingJob);
339  jobs->add(patchingJob);
340  jobs->add(atomToDataJob);
341 
342  m_sigJobCreated->emit(jobs);
343 
344  jobs->run().get();
345 
346  if(jobs->getState() == ::fwJobs::IJob::CANCELED)
347  {
348  return;
349  }
350 
351  FW_RAISE_IF( "Unable to load '" << filePath << "' : invalid data.", !newData );
352 
353  if (m_outputMode)
354  {
355  this->setOutput(::fwIO::s_DATA_KEY, newData);
356  }
357  else
358  {
359  SLM_ASSERT("'" + ::fwIO::s_DATA_KEY + "' key is not defined", data);
360 
361  FW_RAISE_IF( "Unable to load '" << filePath
362  << "' : trying to load a '" << newData->getClassname()
363  << "' where a '" << data->getClassname() << "' was expected",
364  newData->getClassname() != data->getClassname() );
365 
366  // Workaround to read a fwData::Array.
367  // The shallowCopy of a fwData::Array is not allowed due to unknown buffer owner.
368  // So in the case of reading an Array we swap buffers.
369  if(newData->getClassname() == ::fwData::Array::classname())
370  {
371  ::fwData::Array::dynamicCast(data)->swap( ::fwData::Array::dynamicCast(newData) );
372  }
373  else
374  {
375  data->shallowCopy(newData);
376  }
377 
378  this->notificationOfUpdate();
379  }
380  }
381  catch( std::exception& e )
382  {
383  OSLM_ERROR( e.what() );
384  ::fwGui::dialog::MessageDialog::showMessageDialog("Atoms reader failed", e.what(),
385  ::fwGui::dialog::MessageDialog::CRITICAL);
386  }
387  catch( ... )
388  {
389  ::fwGui::dialog::MessageDialog::showMessageDialog("Atoms reader failed", "Aborting operation.",
390  ::fwGui::dialog::MessageDialog::CRITICAL);
391  }
392 
393  cursor.setDefaultCursor();
394 
395  }
396 }
397 
398 //-----------------------------------------------------------------------------
399 
401 {
402  return ::fwIO::FILE;
403 }
404 
405 //------------------------------------------------------------------------------
406 
407 void SReader::notificationOfUpdate()
408 {
409  ::fwData::Object::sptr object = this->getInOut< ::fwData::Object >(::fwIO::s_DATA_KEY);
410  if (!object)
411  {
412  FW_DEPRECATED_KEY(::fwIO::s_DATA_KEY, "inout", "18.0");
413  object = this->getObject< ::fwData::Object >();
414  }
416  {
417  ::fwCom::Connection::Blocker block(sig->getConnection(m_slotUpdate));
418  sig->asyncEmit();
419  }
420 }
421 
422 //-----------------------------------------------------------------------------
423 
425 {
426  static ::boost::filesystem::path _sDefaultPath;
427 
429  dialogFile.setTitle(m_windowTitle.empty() ? "Enter file name" : m_windowTitle);
430  dialogFile.setDefaultLocation( ::fwData::location::Folder::New(_sDefaultPath) );
431  dialogFile.setType(::fwGui::dialog::ILocationDialog::SINGLE_FILE);
432  dialogFile.setOption(::fwGui::dialog::ILocationDialog::READ);
433  dialogFile.setOption(::fwGui::dialog::LocationDialog::FILE_MUST_EXIST);
434 
435  dialogFile.addFilter("Medical data", "*" + ::boost::algorithm::join(m_allowedExts, " *"));
436 
437  for(const std::string& ext : m_allowedExts)
438  {
439  dialogFile.addFilter(m_allowedExtLabels[ext], "*" + ext);
440  }
441 
442  ::fwData::location::SingleFile::sptr result
443  = ::fwData::location::SingleFile::dynamicCast( dialogFile.show() );
444 
445  if (result)
446  {
447  _sDefaultPath = result->getPath();
448  this->setFile( _sDefaultPath );
449  dialogFile.saveDefaultLocation( ::fwData::location::Folder::New(_sDefaultPath.parent_path()) );
450  }
451  else
452  {
453  this->clearLocations();
454  }
455 }
456 
457 //-----------------------------------------------------------------------------
458 
459 } // namespace ioAtoms
#define FW_DEPRECATED_KEY(newKey, access, version)
Use this macro when deprecating a service key to warn the developer.
Definition: spyLog.hpp:366
virtual FWGUI_API void setCursor(::fwGui::ICursor::CursorType cursor) override
Set the cursor.
This policy throws an exception if the loaded uuid is not available.
Definition: AtomVisitor.hpp:62
IOATOMS_API void configuring() override
Parse the configuration.
Definition: SReader.cpp:95
IOATOMS_API void starting() override
Does nothing.
Definition: SReader.cpp:79
std::string m_windowTitle
Title of the window that will open when the configureWithIHM slot is called.
Definition: IReader.hpp:207
FWJOBS_API void doneWork(std::uint64_t units)
Setter on done work units.
Definition: IJob.cpp:379
#define OSLM_ASSERT(message, cond)
work like &#39;assert&#39; from &#39;cassert&#39;, with in addition a message logged by spylog (with FATAL loglevel) ...
Definition: spyLog.hpp:310
Class allowing to block a Connection.
Definition: Connection.hpp:20
IOATOMS_API::fwIO::IOPathType getIOPathType() const override
Returns managed path type, here service manages only single file.
Definition: SReader.cpp:400
FWIO_API bool hasLocationDefined() const
Returns if a location has been defined ( by the configuration process or directly by user ) ...
Definition: IReader.cpp:233
virtual FWIO_API void configuring() override
This method proposes to parse xml configuration to retrieve file/files/folder paths.
Definition: IReader.cpp:144
static FWGUI_API IMessageDialog::Buttons showMessageDialog(const std::string &title, const std::string &message,::fwGui::dialog::IMessageDialog::Icons icon=INFO)
std::map< std::string, std::string > FileExtension2NameType
Maps file extension to format name.
Definition: SReader.hpp:111
static const std::string & classname()
return object&#39;s classname without its namespace, i.e. BaseObject
virtual FWGUI_API void setDefaultLocation(::fwData::location::ILocation::sptr loc) override
Set the initial location for the dialog.
FWGUI_API::fwGui::dialog::ILocationDialog & setOption(::fwGui::dialog::ILocationDialog::Options option) override
allow to set option to the file dialog mode=READ/WRITE, check=FILE_MUST_EXIST
FWGUI_API::fwData::location::ILocation::sptr show() override
Display the dialog.
FWGUI_API void addFilter(const std::string &filterName, const std::string &wildcardList) override
specify some filtering when browsing files:
IOATOMS_API void stopping() override
Does nothing.
Definition: SReader.cpp:85
This policy changes data&#39;s uuid if it already exists.
Definition: AtomVisitor.hpp:55
std::shared_ptr< ::fwJobs::Aggregator > sptr
Aggregator container type.
Definition: Aggregator.hpp:39
FWJOBS_API const bool & cancelRequested() const
Returns the job canceling status.
Definition: IJob.cpp:52
FWIO_APIconst::boost::filesystem::path & getFile() const
Returns the file path set by the user or set during service configuration.
Definition: IReader.cpp:62
UpdateSlotType::sptr m_slotUpdate
Slot to call update method.
Definition: IService.hpp:690
This class encapsulate a task that will report it&#39;s progression The embeded task will be run at most ...
Definition: Job.hpp:30
FWSERVICES_API void setOutput(const ::fwServices::IService::KeyType &key, const ::fwData::Object::sptr &object, size_t index=0)
Register an output object at a given key in the OSR, replacing it if it already exists.
Definition: IService.cpp:80
static FWMDSEMANTICPATCH_API std::string getCurrentVersion()
Returns current MedicalData version.
FWIO_API void setFile(const ::boost::filesystem::path &file)
Sets file path.
Definition: IReader.cpp:71
#define OSLM_ERROR(message)
Definition: spyLog.hpp:274
Reader service API. It manages extension points definition and extension configuration.
Definition: IReader.hpp:34
#define SLM_ASSERT(message, cond)
work like &#39;assert&#39; from &#39;cassert&#39;, with in addition a message logged by spylog (with FATAL loglevel) ...
Definition: spyLog.hpp:308
Manages object patching.
IOATOMS_API void updating() override
Tests file extension, applies the good atom reader, and converts atom in fwData::Composite.
Definition: SReader.cpp:195
static const FileExtension2NameType s_EXTENSIONS
Managed file extensions.
Definition: SReader.hpp:114
This policy reuses the data associated with an existing uuid.
Definition: AtomVisitor.hpp:48
Base class for each data object.
Atoms reader. Service to load data from Atoms format.
Definition: SReader.hpp:89
IOPathType
IOPathType defines different type of paths used by service readers/writers.
Definition: ioTypes.hpp:19
static FWDATA_APIconst::fwCom::Signals::SignalKeyType s_MODIFIED_SIG
Key in m_signals map of signal m_sigModified.
static FWJOBS_API sptr New(const std::string &name, Task task, const std::shared_ptr< ::fwThread::Worker > &worker=nullptr)
Construct a new job and return a smart pointer of it.
Definition: Job.cpp:25
FWGUI_API void setTitle(const std::string &title) override
Set the title for the dialog.
Defines the generic file/folder selector dialog for IHM.
Defines the generic cursor for IHM. Use the Delegate design pattern.
std::shared_ptr< ::fwThread::Worker > m_associatedWorker
Associated worker.
Definition: IService.hpp:699
FWJOBS_API void done()
Set done work units to total work units.
Definition: IJob.cpp:476
Contains services to read and write data via atom conversion.
FWGUI_API void setType(::fwGui::dialog::ILocationDialog::Types type) override
Set the type of location for the dialog (SINGLE_FILE, FORLDER, MULTI_FILES)
FWIO_API void clearLocations()
Clear any location set by the setFile/setFiles/setFolder setter.
Definition: IReader.cpp:137
FWGUI_API void saveDefaultLocation(::fwData::location::ILocation::sptr loc) override
Save the specified default location for the dialog in preferences (if available)
static FWJOBS_API sptr New(const std::string &name="")
Create a new Aggregator smart pointer.
Definition: Aggregator.cpp:24
std::shared_ptr< ::fwJobs::Job > sptr
Task type.
Definition: Job.hpp:36
SReader()
Does nothing.
Definition: SReader.cpp:61
IOATOMS_API void configureWithIHM() override
Propose to choose a medical data file (*.json,*.jsonz,*.xml or *.xmlz)
Definition: SReader.cpp:424
FWSERVICES_API ConfigType getConfigTree() const
Return the configuration, in an boost property tree.
Definition: IService.cpp:247