fw4spl
fwDcmtkIO/src/fwDcmtkIO/SeriesDBReader.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 "fwDcmtkIO/SeriesDBReader.hpp"
8 
9 #include "fwDcmtkIO/helper/Codec.hpp"
10 #include "fwDcmtkIO/helper/DicomDir.hpp"
11 #include "fwDcmtkIO/helper/DicomSearch.hpp"
12 #include "fwDcmtkIO/reader/ImageStorageReader.hpp"
13 
14 #include <fwDataIO/reader/registry/macros.hpp>
15 
16 #include <fwDcmtkTools/Dictionary.hpp>
17 
18 #include <fwDicomIOFilter/composite/CTImageStorageDefaultComposite.hpp>
19 #include <fwDicomIOFilter/exceptions/FilterFailure.hpp>
20 #include <fwDicomIOFilter/helper/Filter.hpp>
21 #include <fwDicomIOFilter/splitter/SOPClassUIDSplitter.hpp>
22 
23 #include <fwMedData/Equipment.hpp>
24 #include <fwMedData/ImageSeries.hpp>
25 #include <fwMedData/ModelSeries.hpp>
26 #include <fwMedData/Patient.hpp>
27 #include <fwMedData/SeriesDB.hpp>
28 #include <fwMedData/Study.hpp>
29 
30 #include <fwMedDataTools/helper/SeriesDB.hpp>
31 
32 #include <dcmtk/config/osconfig.h>
33 #include <dcmtk/dcmdata/dcdeftag.h>
34 #include <dcmtk/dcmdata/dcfilefo.h>
35 #include <dcmtk/dcmdata/dcistrmb.h>
36 #include <dcmtk/dcmnet/diutil.h>
37 
38 fwDataIOReaderRegisterMacro( ::fwDcmtkIO::SeriesDBReader );
39 
40 namespace fwDcmtkIO
41 {
42 
43 //------------------------------------------------------------------------------
44 
45 SeriesDBReader::SeriesDBReader(::fwDataIO::reader::IObjectReader::Key key) :
46  ::fwData::location::enableFolder< IObjectReader >(this),
47  ::fwData::location::enableMultiFiles< IObjectReader >(this),
48  m_isDicomdirActivated(false)
49 {
51 
52  // Load dictionary
54 
55  // Register codecs
57 }
58 
59 //------------------------------------------------------------------------------
60 
61 SeriesDBReader::~SeriesDBReader()
62 {
63  // Clean up codecs
65 }
66 
67 //------------------------------------------------------------------------------
68 
69 SeriesDBReader::FilenameContainerType SeriesDBReader::getFilenames()
70 {
71  FilenameContainerType filenames;
72  if(::fwData::location::have < ::fwData::location::Folder, ::fwDataIO::reader::IObjectReader > (this))
73  {
74  // Try to read dicomdir file
75  if(!m_isDicomdirActivated || (m_isDicomdirActivated &&
77  {
78  // Recursively search for dicom files
80  }
81  }
82  else if(::fwData::location::have < ::fwData::location::MultiFiles, ::fwDataIO::reader::IObjectReader > (this))
83  {
84  for(::boost::filesystem::path file: this->getFiles())
85  {
86  filenames.push_back(file.string());
87  }
88  }
89 
90  return filenames;
91 }
92 
93 //------------------------------------------------------------------------------
94 
96 {
98 
99  // Get filenames
100  FilenameContainerType filenames = this->getFilenames();
101 
102  // Read Dicom Series
103  this->addSeries(filenames);
104 
105  // Apply Default filters
106  if(!m_dicomFilterType.empty())
107  {
108  ::fwDicomIOFilter::IFilter::sptr filter = ::fwDicomIOFilter::factory::New(m_dicomFilterType);
109  SLM_ASSERT("Failed to instantiate filter of type '" + m_dicomFilterType + "'.", filter);
110  ::fwDicomIOFilter::helper::Filter::applyFilter(m_dicomSeriesContainer, filter, true);
111  }
112 
113  // Read series
114  for(::fwMedData::DicomSeries::csptr series: m_dicomSeriesContainer)
115  {
116  this->convertDicomSeries(series);
117  }
118 }
119 
120 //------------------------------------------------------------------------------
121 
122 void SeriesDBReader::readFromDicomSeriesDB(::fwMedData::SeriesDB::csptr dicomSeriesDB,
123  ::fwServices::IService::sptr notifier)
124 {
125  // Read series
126  for(const ::fwMedData::Series::csptr& series : dicomSeriesDB->getContainer())
127  {
128  ::fwMedData::DicomSeries::csptr dicomSeries = ::fwMedData::DicomSeries::dynamicCast(series);
129  OSLM_ASSERT("Trying to read a series which is not a DicomSeries.", dicomSeries);
130  this->convertDicomSeries(dicomSeries, notifier);
131  }
132 }
133 
134 //------------------------------------------------------------------------------
135 
136 void SeriesDBReader::readDicomSeries()
137 {
138  ::fwMedData::SeriesDB::sptr seriesDB = this->getConcreteObject();
139  ::fwMedDataTools::helper::SeriesDB seriesDBHelper(seriesDB);
140 
141  // Get filenames
142  FilenameContainerType filenames = this->getFilenames();
143 
144  // Read Dicom Series
145  this->addSeries(filenames);
146 
147  // Push Dicom Series
148  for(::fwMedData::DicomSeries::sptr series: m_dicomSeriesContainer)
149  {
150  seriesDBHelper.add(series);
151  }
152 }
153 
154 //------------------------------------------------------------------------------
155 
156 bool SeriesDBReader::isDicomDirAvailable()
157 {
158  std::vector<std::string> filenames;
159  bool result = ::fwDcmtkIO::helper::DicomDir::readDicomDir(this->getFolder(), filenames);
160  return result && !filenames.empty();
161 }
162 
163 //------------------------------------------------------------------------------
164 
165 void SeriesDBReader::addSeries(const std::vector< std::string >& filenames)
166 {
167  for(const std::string& filename : filenames)
168  {
169  DcmFileFormat fileFormat;
170  OFCondition status = fileFormat.loadFile(filename.c_str());
171  FW_RAISE_IF("Unable to read the file: \""+filename+"\"", status.bad());
172 
173  DcmDataset* dataset = fileFormat.getDataset();
174 
175  // Create Series
176  this->createSeries(dataset, filename);
177  }
178 
179  // Fill series
180  for(const ::fwMedData::DicomSeries::sptr& series : m_dicomSeriesContainer)
181  {
182  // Compute number of instances
183  series->setNumberOfInstances(series->getDicomContainer().size());
184 
185  // Get first instance
186  const auto& bufferObj = series->getDicomContainer().begin()->second;
187  const size_t buffSize = bufferObj->getSize();
188  ::fwMemory::BufferObject::Lock lock(bufferObj);
189  char* buffer = static_cast< char* >( lock.getBuffer() );
190 
191  DcmInputBufferStream is;
192  is.setBuffer(buffer, offile_off_t(buffSize));
193  is.setEos();
194 
195  // Load first instance
196  DcmFileFormat fileFormat;
197  fileFormat.transferInit();
198  if (!fileFormat.read(is).good())
199  {
200  FW_RAISE("Unable to read Dicom file '"<< bufferObj->getStreamInfo().fsFile.string() <<"'");
201  }
202 
203  fileFormat.loadAllDataIntoMemory();
204  fileFormat.transferEnd();
205 
206  DcmDataset* dataset = fileFormat.getDataset();
207 
208  // Create data objects from first instance
209  ::fwMedData::Patient::sptr patient = this->createPatient(dataset);
210  ::fwMedData::Study::sptr study = this->createStudy(dataset);
211  ::fwMedData::Equipment::sptr equipment = this->createEquipment(dataset);
212 
213  // Fill series
214  series->setPatient(patient);
215  series->setStudy(study);
216  series->setEquipment(equipment);
217  }
218 }
219 
220 //------------------------------------------------------------------------------
221 
222 ::fwMedData::Patient::sptr SeriesDBReader::createPatient(DcmDataset* dataset)
223 {
224  ::fwMedData::Patient::sptr result;
225  OFString data;
226 
227  // Get Patient ID
228  dataset->findAndGetOFStringArray(DCM_PatientID, data);
229  ::std::string patientID = data.c_str();
230 
231  // Check if the patient already exists
232  if(m_patientMap.find(patientID) == m_patientMap.end())
233  {
234  result = ::fwMedData::Patient::New();
235  m_patientMap[patientID] = result;
236 
237  //Patient ID
238  result->setPatientId(patientID);
239 
240  //Patient Name
241  dataset->findAndGetOFStringArray(DCM_PatientName, data);
242  result->setName(data.c_str());
243 
244  //Patient Birthday
245  dataset->findAndGetOFStringArray(DCM_PatientBirthDate, data);
246  result->setBirthdate(data.c_str());
247 
248  //Patient Sex
249  dataset->findAndGetOFStringArray(DCM_PatientSex, data);
250  result->setSex(data.c_str());
251 
252  }
253  else
254  {
255  result = m_patientMap[patientID];
256  }
257 
258  return result;
259 }
260 
261 //------------------------------------------------------------------------------
262 
263 ::fwMedData::Study::sptr SeriesDBReader::createStudy(DcmDataset* dataset)
264 {
265  ::fwMedData::Study::sptr result;
266  OFString data;
267 
268  // Get Study ID
269  dataset->findAndGetOFStringArray(DCM_StudyInstanceUID, data);
270  ::std::string studyID = data.c_str();
271 
272  // Check if the study already exists
273  if(m_studyMap.find(studyID) == m_studyMap.end())
274  {
275  result = ::fwMedData::Study::New();
276  m_studyMap[studyID] = result;
277 
278  //Study ID
279  result->setInstanceUID(studyID);
280 
281  //Study Date
282  dataset->findAndGetOFStringArray(DCM_StudyDate, data);
283  result->setDate(data.c_str());
284 
285  //Study Time
286  dataset->findAndGetOFStringArray(DCM_StudyTime, data);
287  result->setTime(data.c_str());
288 
289  //Referring Physician Name
290  dataset->findAndGetOFStringArray(DCM_ReferringPhysicianName, data);
291  result->setReferringPhysicianName(data.c_str());
292 
293  //Study Description
294  dataset->findAndGetOFStringArray(DCM_StudyDescription, data);
295  result->setDescription(data.c_str());
296 
297  //Study Patient Age
298  dataset->findAndGetOFStringArray(DCM_PatientAge, data);
299  result->setPatientAge(data.c_str());
300 
301  }
302  else
303  {
304  result = m_studyMap[studyID];
305  }
306 
307  return result;
308 }
309 
310 //------------------------------------------------------------------------------
311 
312 ::fwMedData::Equipment::sptr SeriesDBReader::createEquipment(DcmDataset* dataset)
313 {
314  ::fwMedData::Equipment::sptr result;
315  OFString data;
316 
317  // Get Institution Name
318  dataset->findAndGetOFStringArray(DCM_InstitutionName, data);
319  ::std::string institutionName = data.c_str();
320 
321  // Check if the equipment already exists
322  if(m_equipmentMap.find(institutionName) == m_equipmentMap.end())
323  {
324  result = ::fwMedData::Equipment::New();
325  m_equipmentMap[institutionName] = result;
326 
327  //Institution Name
328  result->setInstitutionName(institutionName);
329 
330  }
331  else
332  {
333  result = m_equipmentMap[institutionName];
334  }
335 
336  return result;
337 }
338 
339 //------------------------------------------------------------------------------
340 
341 void SeriesDBReader::createSeries(DcmDataset* dataset, const std::string& filename)
342 {
343  ::fwMedData::DicomSeries::sptr series = ::fwMedData::DicomSeries::sptr();
344  OFString data;
345 
346  // Get Series Instance UID
347  dataset->findAndGetOFStringArray(DCM_SeriesInstanceUID, data);
348  std::string seriesInstanceUID = data.c_str();
349 
350  // Check if the series already exists
351  for(const ::fwMedData::DicomSeries::sptr& dicomSeries : m_dicomSeriesContainer)
352  {
353  if(dicomSeries->getInstanceUID() == seriesInstanceUID)
354  {
355  series = dicomSeries;
356  break;
357  }
358  }
359 
360  // If the series doesn't exist we create it
361  if(!series)
362  {
363  series = ::fwMedData::DicomSeries::New();
364  m_dicomSeriesContainer.push_back(series);
365 
366  //Instance UID
367  series->setInstanceUID(seriesInstanceUID);
368 
369  //Modality
370  dataset->findAndGetOFStringArray(DCM_Modality, data);
371  series->setModality(data.c_str());
372 
373  //Date
374  dataset->findAndGetOFStringArray(DCM_SeriesDate, data);
375  series->setDate(data.c_str());
376 
377  //Time
378  dataset->findAndGetOFStringArray(DCM_SeriesTime, data);
379  series->setTime(data.c_str());
380 
381  //Description
382  dataset->findAndGetOFStringArray(DCM_SeriesDescription, data);
383  series->setDescription(data.c_str());
384 
385  //Performing Physicians Name
386  std::vector<std::string> performingPhysiciansName;
387  for(int i = 0; dataset->findAndGetOFString(DCM_PerformingPhysicianName, data, i).good(); ++i)
388  {
389  performingPhysiciansName.push_back(data.c_str());
390  }
391  series->setPerformingPhysiciansName(performingPhysiciansName);
392  }
393 
394  // Add the SOPClassUID to the series
395  dataset->findAndGetOFStringArray(DCM_SOPClassUID, data);
396  ::fwMedData::DicomSeries::SOPClassUIDContainerType sopClassUIDContainer = series->getSOPClassUIDs();
397  sopClassUIDContainer.insert(data.c_str());
398  series->setSOPClassUIDs(sopClassUIDContainer);
399 
400  // Add the instance to the series
401  const std::size_t instanceNumber = series->getDicomContainer().size();
402  series->addDicomPath(instanceNumber, filename);
403 }
404 
405 //------------------------------------------------------------------------------
406 
407 void SeriesDBReader::convertDicomSeries(::fwMedData::DicomSeries::csptr dicomSeries,
408  ::fwServices::IService::sptr notifier)
409 {
410  ::fwMedData::SeriesDB::sptr seriesDB = this->getConcreteObject();
411  ::fwMedDataTools::helper::SeriesDB seriesDBHelper(seriesDB);
412  ::fwMedData::Series::sptr result = ::fwMedData::Series::sptr();
413 
414  ::fwMedData::DicomSeries::SOPClassUIDContainerType sopClassUIDContainer = dicomSeries->getSOPClassUIDs();
415  FW_RAISE_IF("The series contains several SOPClassUIDs. Try to apply a filter in order to split the series.",
416  sopClassUIDContainer.size() != 1);
417  std::string sopClassUID = sopClassUIDContainer.begin()->c_str();
418 
419  const SupportedSOPClassContainerType::iterator bIt = m_supportedSOPClassContainer.begin();
420  const SupportedSOPClassContainerType::iterator eIt = m_supportedSOPClassContainer.end();
421 
422  if(m_supportedSOPClassContainer.empty() || std::find(bIt, eIt, sopClassUID) != eIt)
423  {
425  result = reader.read(dicomSeries);
426  }
427 
428  if(result)
429  {
430  // Add the series to the DB
431  seriesDBHelper.add(result);
432  }
433  else
434  {
435  OSLM_WARN("\""+sopClassUID+"\" SOPClassUID is not supported.");
436  }
437 
438  if(notifier)
439  {
440  seriesDBHelper.notify();
441  }
442 
443 }
444 
445 //------------------------------------------------------------------------------
446 
447 SeriesDBReader::DicomSeriesContainerType& SeriesDBReader::getDicomSeries()
448 {
449  return m_dicomSeriesContainer;
450 }
451 
452 } //namespace fwDcmtkIO
static FWDCMTKTOOLS_API void loadDictionary()
Load the DICOM dictionary.
Definition: Dictionary.cpp:25
ILocation::PathType getFolder()
Get folder filesystem path.
Definition: Folder.hpp:99
#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
#define SLM_TRACE_FUNC()
Trace contextual function signature.
Definition: spyLog.hpp:329
Reads DICOM data from a directory path in order to create a SeriesDB object.
static FWDCMTKIO_API void registerCodecs()
Load DICOM codec.
Definition: Codec.cpp:18
VTKGDCMIO_API void read() override
Reads DICOM data from configured path and fills SeriesDB object.
Key class used to restrict access to Object construction. See http://www.drdobbs.com/184402053.
static FWDCMTKIO_API bool readDicomDir(const ::boost::filesystem::path &root, std::vector< std::string > &dicomFiles)
Find Dicom instances in a DicomDir file.
FWMEDDATATOOLS_API void add(::fwMedData::Series::sptr newSeries)
Add a Series in the SeriesDB.
base class for BufferObject Lock
Defines an helper to modify an fwMedData::SeriesDB and create in parallel the message to announce thi...
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
LockBase< T >::BufferType getBuffer() const
Returns BufferObject&#39;s buffer pointer.
#define OSLM_WARN(message)
Definition: spyLog.hpp:263
Base class for Dicom instance reader.
#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
static FWDCMTKIO_API void searchRecursively(const ::boost::filesystem::path &dirPath, std::vector< std::string > &dicomFiles)
Search Dicom files recursively.
virtual std::shared_ptr< DataType > getConcreteObject()
m_object getter.
virtual FWDCMTKIO_API::fwMedData::Series::sptr read(const ::fwMedData::DicomSeries::csptr &series)
Override.
static FWDCMTKIO_API void cleanup()
Clean up codec register.
Definition: Codec.cpp:29
fwDcmtkIO contains classes used to pull Dicom images from a pacs using dcmtk library.
Definition: Codec.hpp:12
ILocation::VectPathType getFiles()
Get file system paths.
Definition: MultiFiles.hpp:77
Contains the representation of the data objects used in the framework.
FWMEDDATATOOLS_API void notify()
Send the signal of modification.