fw4spl
io/fwGdcmIO/src/fwGdcmIO/reader/SeriesDB.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 "fwGdcmIO/reader/SeriesDB.hpp"
8 
9 #include "fwGdcmIO/helper/DicomDir.hpp"
10 #include "fwGdcmIO/helper/DicomSearch.hpp"
11 #include "fwGdcmIO/helper/DicomSeries.hpp"
12 #include "fwGdcmIO/helper/SOPClass.hpp"
13 #include "fwGdcmIO/reader/Series.hpp"
14 
15 #include <fwDataIO/reader/registry/macros.hpp>
16 
17 #include <fwDicomIOFilter/factory/new.hpp>
18 #include <fwDicomIOFilter/helper/Filter.hpp>
19 #include <fwDicomIOFilter/IFilter.hpp>
20 
21 #include <fwJobs/Aggregator.hpp>
22 #include <fwJobs/Job.hpp>
23 #include <fwJobs/Observer.hpp>
24 
25 #include <fwMedDataTools/helper/SeriesDB.hpp>
26 
27 #include <fwServices/registry/ActiveWorkers.hpp>
28 
29 #include <boost/algorithm/string/split.hpp>
30 
31 #include <gdcmAttribute.h>
32 #include <gdcmDirectory.h>
33 #include <gdcmMediaStorage.h>
34 #include <gdcmUIDs.h>
35 
36 fwDataIOReaderRegisterMacro( ::fwGdcmIO::reader::SeriesDB );
37 
38 namespace fwGdcmIO
39 {
40 
41 namespace reader
42 {
43 
44 //------------------------------------------------------------------------------
45 
47  ::fwData::location::enableFolder< IObjectReader >(this),
48  ::fwData::location::enableMultiFiles< IObjectReader >(this),
49  m_isDicomdirActivated(false),
50  m_dicomFilterType(""),
51  m_logger(::fwLog::Logger::New()),
52  m_job(::fwJobs::Aggregator::New("DICOM reader")),
53  m_enableBufferRotation(true),
54  m_dicomdirFileLookupJob(::fwJobs::Observer::New("Extracting information from DICOMDIR")),
55  m_regularFileLookupJob(::fwJobs::Observer::New("Looking for DICOM files")),
56  m_readerJob(::fwJobs::Observer::New("Reading DICOM files")),
57  m_completeDicomSeriesJob(::fwJobs::Observer::New("Completing series")),
58  m_converterJob(::fwJobs::Observer::New("DICOM data convertion"))
59 {
61 }
62 
63 //------------------------------------------------------------------------------
64 
66 {
68 }
69 
70 //------------------------------------------------------------------------------
71 
73 {
74  // Clear DicomSeries container
75  m_dicomSeriesContainer.clear();
76 
77  m_job->add(m_dicomdirFileLookupJob);
78  m_job->add(m_regularFileLookupJob);
79  m_job->add(m_readerJob);
80  m_job->add(m_completeDicomSeriesJob);
81  m_job->add(m_converterJob);
82 
83  try
84  {
85  this->readDicom();
86  }
87  catch (const std::exception& e)
88  {
89  m_logger->clear();
90  m_logger->critical("An error has occurred during the reading process : unable to retrieve series.");
91 
92  // Finish jobs
93  m_dicomdirFileLookupJob->finish();
94  m_regularFileLookupJob->finish();
95  m_readerJob->finish();
96  m_completeDicomSeriesJob->finish();
97  m_converterJob->finish();
98  m_job->run().get();
99  return;
100  }
101 
102  // Apply Default filters
103  if(!m_dicomFilterType.empty())
104  {
105  ::fwDicomIOFilter::IFilter::sptr filter = ::fwDicomIOFilter::factory::New(m_dicomFilterType);
106  ::fwDicomIOFilter::helper::Filter::applyFilter(m_dicomSeriesContainer, filter, true, m_logger);
107  }
108 
109  if(m_dicomSeriesContainer.empty())
110  {
111  m_logger->critical("Unable to retrieve series from the selected folder.");
112  m_converterJob->done();
113  m_converterJob->finish();
114  }
115  else
116  {
117  // Read series
118  this->convertDicomSeries();
119  }
120 
121  try
122  {
123  // .get() throws exception that might have occurred
124  m_job->run().get();
125  }
126  catch (const std::exception& e)
127  {
128  m_logger->critical("An error has occurred during the reading process : " + std::string(e.what()));
129  }
130  catch( ... )
131  {
132  m_logger->critical("An unkown error has occurred during the reading process.");
133  }
134 
135  if(m_dicomSeriesContainer.empty())
136  {
137  m_logger->critical("Unable to retrieve series from the selected folder.");
138  }
139 }
140 
141 //------------------------------------------------------------------------------
142 
144 {
145  // Clear DicomSeries container
146  m_dicomSeriesContainer.clear();
147 
148  m_job->add(m_dicomdirFileLookupJob);
149  m_job->add(m_regularFileLookupJob);
150  m_job->add(m_readerJob);
151  m_job->add(m_completeDicomSeriesJob);
152 
153  try
154  {
155  this->readDicom();
156  }
157  catch (const std::exception& e)
158  {
159  m_logger->clear();
160  m_logger->critical("An error has occurred during the reading process : unable to retrieve series.");
161 
162  // Finish jobs
163  m_dicomdirFileLookupJob->finish();
164  m_regularFileLookupJob->finish();
165  m_readerJob->finish();
166  m_completeDicomSeriesJob->finish();
167  m_job->run().get();
168  return;
169  }
170 
171  ::fwMedData::SeriesDB::sptr seriesDB = this->getConcreteObject();
172  ::fwMedDataTools::helper::SeriesDB seriesDBHelper(seriesDB);
173 
174  // Push Dicom Series
175  if(!m_job->cancelRequested())
176  {
177  for(::fwMedData::DicomSeries::sptr series : m_dicomSeriesContainer)
178  {
179  seriesDBHelper.add(series);
180  }
181  }
182 
183  try
184  {
185  // .get() throws exception that might have occurred
186  m_job->run().get();
187  }
188  catch (const std::exception& e)
189  {
190  m_logger->critical("An error has occurred during the reading process : " + std::string(e.what()));
191  }
192  catch( ... )
193  {
194  m_logger->critical("An unkown error has occurred during the reading process.");
195  }
196 
197  if(m_dicomSeriesContainer.empty())
198  {
199  m_logger->critical("Unable to retrieve series from the selected folder.");
200  }
201 
202 }
203 
204 //------------------------------------------------------------------------------
205 
206 void SeriesDB::readDicom()
207 {
208  SLM_ASSERT("This reader only work on folder selection.",
209  (::fwData::location::have < ::fwData::location::Folder, ::fwDataIO::reader::IObjectReader > (this)));
210 
211  // DICOMDIR
212  auto dicomdir = ::fwGdcmIO::helper::DicomDir::findDicomDir(this->getFolder());
213  if(m_isDicomdirActivated && ::boost::filesystem::exists(dicomdir))
214  {
215  // Create Dicom Series
217  m_dicomSeriesContainer,
218  m_logger,
219  m_readerJob->progressCallback(),
220  m_readerJob->cancelRequestedCallback()
221  );
222  // Fill Dicom Series
224  helper.complete(m_dicomSeriesContainer, m_completeDicomSeriesJob);
225 
226  }
227 
228  // Finish DICOMDIR lookup
229  m_dicomdirFileLookupJob->done();
230  m_dicomdirFileLookupJob->finish();
231 
232  // Regular read
233  if(!m_isDicomdirActivated || !::boost::filesystem::exists(dicomdir) || m_dicomSeriesContainer.empty())
234  {
235  m_readerJob->doneWork(0);
236 
237  // Recursively search for dicom files
238  std::vector< ::boost::filesystem::path > filenames;
240  this->getFolder(), filenames, true, m_regularFileLookupJob);
241 
242  // Read Dicom Series
244  m_dicomSeriesContainer = helper.read(filenames, m_readerJob, m_completeDicomSeriesJob);
245  }
246 
247  // Finish regular lookup
248  m_regularFileLookupJob->done();
249  m_regularFileLookupJob->finish();
250 
251  // Finish reading
252  m_readerJob->done();
253  m_readerJob->finish();
254 
255  // Finish completing series
256  m_completeDicomSeriesJob->done();
257  m_completeDicomSeriesJob->finish();
258 
259 }
260 
261 //------------------------------------------------------------------------------
262 
263 void SeriesDB::readFromDicomSeriesDB(const ::fwMedData::SeriesDB::csptr& dicomSeriesDB,
264  const ::fwServices::IService::sptr& notifier)
265 {
266  // Clear DicomSeries container
267  m_dicomSeriesContainer.clear();
268 
269  m_job->add(m_converterJob);
270 
271  // Read series
272  for(::fwMedData::Series::sptr series : dicomSeriesDB->getContainer())
273  {
274  ::fwMedData::DicomSeries::sptr dicomSeries = ::fwMedData::DicomSeries::dynamicCast(series);
275  SLM_ASSERT("Trying to read a series which is not a DicomSeries.", dicomSeries);
276  m_dicomSeriesContainer.push_back(dicomSeries);
277  }
278 
279  // Apply Default filters
280  if(!m_dicomFilterType.empty())
281  {
282  ::fwDicomIOFilter::IFilter::sptr filter = ::fwDicomIOFilter::factory::New(m_dicomFilterType);
283  ::fwDicomIOFilter::helper::Filter::applyFilter(m_dicomSeriesContainer, filter, true, m_logger);
284  }
285 
286  if(m_dicomSeriesContainer.empty())
287  {
288  m_logger->critical("Unable to retrieve series from the selected folder.");
289  m_converterJob->done();
290  m_converterJob->finish();
291  }
292  else
293  {
294  // Read series
295  this->convertDicomSeries(notifier);
296  }
297 
298  try
299  {
300  // .get() throws exception that might have occurred
301  m_job->run().get();
302  }
303  catch (const std::exception& e)
304  {
305  m_logger->critical("An error has occurred during the reading process : " + std::string(e.what()));
306  }
307  catch( ... )
308  {
309  m_logger->critical("An unkown error has occurred during the reading process.");
310  }
311 
312 }
313 
314 //------------------------------------------------------------------------------
315 
317 {
318  auto dicomdir = ::fwGdcmIO::helper::DicomDir::findDicomDir(this->getFolder());
319  return ::boost::filesystem::exists(dicomdir);
320 }
321 
322 //------------------------------------------------------------------------------
323 
324 void SeriesDB::convertDicomSeries(const ::fwServices::IService::sptr& notifier)
325 {
326  ::fwMedData::SeriesDB::sptr seriesDB = this->getConcreteObject();
327 
328  // Sort DicomSeries
329  std::sort(m_dicomSeriesContainer.begin(), m_dicomSeriesContainer.end(), SeriesDB::dicomSeriesComparator);
330 
331  // Create reader
332  SPTR(::fwGdcmIO::reader::Series) seriesReader = std::make_shared< ::fwGdcmIO::reader::Series >();
333  seriesReader->setBufferRotationEnabled(m_enableBufferRotation);
334  seriesReader->setLogger(m_logger);
335 
336  m_converterJob->setTotalWorkUnits(m_dicomSeriesContainer.size());
337 
338  // Compute total work units
339  // We do not use an Aggregator here as the jobs
340  // are created after updating the main aggregator.
341  std::uint64_t totalWorkUnits = 0;
342  for(const ::fwMedData::DicomSeries::sptr& dicomSeries : m_dicomSeriesContainer)
343  {
344  totalWorkUnits += dicomSeries->getDicomContainer().size();
345  }
346  m_converterJob->setTotalWorkUnits(totalWorkUnits);
347 
348  std::uint64_t completedProgress = 0;
349  auto progressCallback = [&](std::uint64_t progress)
350  {
351  m_converterJob->doneWork(completedProgress + progress);
352  };
353 
354  // Read series
355  for(const ::fwMedData::DicomSeries::csptr& dicomSeries : m_dicomSeriesContainer)
356  {
357  ::fwMedData::DicomSeries::SOPClassUIDContainerType sopClassUIDContainer = dicomSeries->getSOPClassUIDs();
358  FW_RAISE_IF("The series contains several SOPClassUIDs. Try to apply a filter in order to split the series.",
359  sopClassUIDContainer.size() != 1);
360  const std::string sopClassUID = sopClassUIDContainer.begin()->c_str();
361 
362  const SupportedSOPClassContainerType::iterator bIt = m_supportedSOPClassContainer.begin();
363  const SupportedSOPClassContainerType::iterator eIt = m_supportedSOPClassContainer.end();
364 
365  if(m_supportedSOPClassContainer.empty() || std::find(bIt, eIt, sopClassUID) != eIt)
366  {
367  seriesReader->setProgressCallback(progressCallback);
368  seriesReader->setCancelRequestedCallback(m_converterJob->cancelRequestedCallback());
369  try
370  {
371  ::fwMedData::Series::sptr series = seriesReader->read(dicomSeries);
372 
373  if(series)
374  {
375  // Add the series to the DB
376  ::fwMedDataTools::helper::SeriesDB seriesDBHelper(seriesDB);
377  seriesDBHelper.add(series);
378 
379  if(notifier)
380  {
381  seriesDBHelper.notify();
382  }
383  }
384  }
385  catch (::fwGdcmIO::exception::Failed& e)
386  {
387  m_logger->critical("Unable to read series : " + dicomSeries->getInstanceUID());
388  }
389  }
390  else
391  {
392  const std::string sopClassName = ::fwGdcmIO::helper::SOPClass::getSOPClassName(sopClassUID);
393  m_logger->critical("DICOM SOP Class \"" + sopClassName +"\" is not supported by the selected reader.");
394  }
395 
396  if(m_job->cancelRequested())
397  {
398  break;
399  }
400 
401  completedProgress = m_converterJob->getDoneWorkUnits();
402  }
403 
404  m_converterJob->done();
405  m_converterJob->finish();
406 
407 }
408 
409 //------------------------------------------------------------------------------
410 
411 bool SeriesDB::dicomSeriesComparator(const SPTR(::fwMedData::DicomSeries)& a,
412  const SPTR(::fwMedData::DicomSeries)& b)
413 {
414  const ::fwMedData::DicomSeries::SOPClassUIDContainerType aSOPClassUIDContainer = a->getSOPClassUIDs();
415  const std::string aSOPClassUID = *(aSOPClassUIDContainer.begin());
416  const ::fwMedData::DicomSeries::SOPClassUIDContainerType bSOPClassUIDContainer = b->getSOPClassUIDs();
417  const std::string bSOPClassUID = *(bSOPClassUIDContainer.begin());
418 
419  // a > b if a contains a SR and not b
420  const bool aIsAnImage =
421  (::gdcm::MediaStorage::GetMSType(aSOPClassUID.c_str()) == ::gdcm::MediaStorage::EnhancedSR ||
422  ::gdcm::MediaStorage::GetMSType(aSOPClassUID.c_str()) == ::gdcm::MediaStorage::ComprehensiveSR ||
423  aSOPClassUID == "1.2.840.10008.5.1.4.1.1.88.34" || // FIXME Replace hard coded string by
424  // "::gdcm::MediaStorage::GetMSType(aSOPClassUID.c_str()) ==
425  // ::gdcm::MediaStorage::Comprehensive3DSR"
426  ::gdcm::MediaStorage::GetMSType(aSOPClassUID.c_str()) == ::gdcm::MediaStorage::SpacialFiducialsStorage ||
427  ::gdcm::MediaStorage::GetMSType(aSOPClassUID.c_str()) == ::gdcm::MediaStorage::SurfaceSegmentationStorage);
428 
429  const bool bIsAnImage =
430  (::gdcm::MediaStorage::GetMSType(bSOPClassUID.c_str()) == ::gdcm::MediaStorage::EnhancedSR ||
431  ::gdcm::MediaStorage::GetMSType(bSOPClassUID.c_str()) == ::gdcm::MediaStorage::ComprehensiveSR ||
432  bSOPClassUID == "1.2.840.10008.5.1.4.1.1.88.34" || // FIXME Replace hard coded string by
433  // "::gdcm::MediaStorage::GetMSType(bSOPClassUID.c_str()) ==
434  // ::gdcm::MediaStorage::Comprehensive3DSR"
435  ::gdcm::MediaStorage::GetMSType(bSOPClassUID.c_str()) == ::gdcm::MediaStorage::SpacialFiducialsStorage ||
436  ::gdcm::MediaStorage::GetMSType(aSOPClassUID.c_str()) == ::gdcm::MediaStorage::SurfaceSegmentationStorage);
437 
438  return bIsAnImage && !aIsAnImage;
439 }
440 
441 //------------------------------------------------------------------------------
442 
443 SeriesDB::DicomSeriesContainerType& SeriesDB::getDicomSeries()
444 {
445  return m_dicomSeriesContainer;
446 }
447 
448 //------------------------------------------------------------------------------
449 
451 {
452  return m_job;
453 }
454 
455 //------------------------------------------------------------------------------
456 
457 } // namespace reader
458 
459 } // namespace fwGdcmIO
FWGDCMIO_API DicomSeriesContainerType & getDicomSeries()
Return DicomSeries container.
#define SPTR(_cls_)
ILocation::PathType getFolder()
Get folder filesystem path.
Definition: Folder.hpp:99
#define SLM_TRACE_FUNC()
Trace contextual function signature.
Definition: spyLog.hpp:329
This class is an interface for class managing job.
Definition: IJob.hpp:28
static FWGDCMIO_API::boost::filesystem::path findDicomDir(const ::boost::filesystem::path &root)
Find the DICOMDIR file in the parent arborescence.
Key class used to restrict access to Object construction. See http://www.drdobbs.com/184402053.
FWGDCMIO_API void read() override
Reads DICOM data from configured path and fills SeriesDB object.
FWGDCMIO_API SeriesDB(::fwDataIO::reader::IObjectReader::Key key)
Constructor.
FWMEDDATATOOLS_API void add(::fwMedData::Series::sptr newSeries)
Add a Series in the SeriesDB.
Defines an helper to modify an fwMedData::SeriesDB and create in parallel the message to announce thi...
The namespace fwGdcmIO contains reader, writer and helper for dicom data.
static FWDICOMIOFILTER_API bool applyFilter(DicomSeriesContainerType &dicomSeriesContainer,::fwDicomIOFilter::IFilter::sptr filter, bool forcedApply=false, const ::fwLog::Logger::sptr &logger=::fwLog::Logger::New())
Apply a filter to the DicomSeries.
Definition: Filter.cpp:17
FWGDCMIO_API std::shared_ptr< ::fwJobs::IJob > getJob() const override
Getter for reader&#39;s job.
fwLog contains classes used to manage logs.
Definition: Log.hpp:16
Implements a failed exception class for fwGdcmIO.
This class adds patient(s) from DICOM file(s) to fwData::SeriesDB.
FWGDCMIO_API void readFromDicomSeriesDB(const ::fwMedData::SeriesDB::csptr &dicomSeriesDB, const ::fwServices::IService::sptr &notifier=::fwServices::IService::sptr())
Reads DICOM data from DicomSeries and fills SeriesDB object.
static FWGDCMIO_API void searchRecursively(const ::boost::filesystem::path &dirPath, std::vector< ::boost::filesystem::path > &dicomFiles, bool checkIsDicom, const std::shared_ptr< ::fwJobs::Observer > &fileLookupObserver=nullptr)
Search Dicom files recursively by excluding files with known extensions.
FWGDCMIO_API bool isDicomDirAvailable()
Return true if a dicomdir file can be read.
#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
FWGDCMIO_API DicomSeriesContainerType read(FilenameContainerType &filenames, const std::shared_ptr< ::fwJobs::Observer > &readerObserver=nullptr, const std::shared_ptr< ::fwJobs::Observer > &completeSeriesObserver=nullptr)
Read DicomSeries from paths.
virtual std::shared_ptr< DataType > getConcreteObject()
m_object getter.
const SOPClassUIDContainerType & getSOPClassUIDs() const
SOP Class UID.
FWGDCMIO_API void complete(DicomSeriesContainerType &seriesDB, const std::shared_ptr< ::fwJobs::Observer > &completeSeriesObserver)
Fill DicomSeries information for series generated using DICOMDIR helper.
static FWGDCMIO_API std::string getSOPClassName(const std::string &SOPClassUID)
Returns SOP Class Name.
Definition: SOPClass.cpp:445
Contains the representation of the data objects used in the framework.
FWMEDDATATOOLS_API void notify()
Send the signal of modification.
FWGDCMIO_API void readDicomSeries()
Reads DICOM data from configured path and fills SeriesDB object with DicomSeries. ...
static FWGDCMIO_API void retrieveDicomSeries(const ::boost::filesystem::path &dicomdir, std::vector< std::shared_ptr< ::fwMedData::DicomSeries > > &seriesDB, const std::shared_ptr< ::fwLog::Logger > &logger, std::function< void(std::uint64_t) > progress=nullptr, std::function< bool() > cancel=nullptr)
Create DicomSeries from information stored in DICOMDIR.
DicomSeries Helper. This class is used to generate/fill DicomSeries.
This namespace fwJobs provides jobs management.