fw4spl
io/fwGdcmIO/src/fwGdcmIO/helper/DicomSeries.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/helper/DicomSeries.hpp"
8 
9 #include "fwGdcmIO/helper/DicomDir.hpp"
10 
11 #include <fwCore/exceptionmacros.hpp>
12 #include <fwCore/spyLog.hpp>
13 
14 #include <fwJobs/Aggregator.hpp>
15 #include <fwJobs/IJob.hpp>
16 #include <fwJobs/Job.hpp>
17 #include <fwJobs/Observer.hpp>
18 
19 #include <fwMedData/DicomSeries.hpp>
20 #include <fwMedData/Equipment.hpp>
21 #include <fwMedData/Patient.hpp>
22 #include <fwMedData/Study.hpp>
23 
24 #include <boost/algorithm/string.hpp>
25 #include <boost/filesystem/operations.hpp>
26 #include <boost/regex.h>
27 
28 #include <gdcmMediaStorage.h>
29 #include <gdcmReader.h>
30 
31 #include <algorithm>
32 
33 namespace fwGdcmIO
34 {
35 namespace helper
36 {
37 
38 //Series
39 static const ::gdcm::Tag s_MediaStorageSOPClassUID(0x0002, 0x0002);
40 static const ::gdcm::Tag s_SpecificCharacterSetTag(0x0008, 0x0005);
41 static const ::gdcm::Tag s_SeriesInstanceUIDTag(0x0020, 0x000e);
42 static const ::gdcm::Tag s_SeriesDateTag(0x0008, 0x0021);
43 static const ::gdcm::Tag s_SeriesTimeTag(0x0008, 0x0031);
44 static const ::gdcm::Tag s_ModalityTag(0x0008, 0x0060);
45 static const ::gdcm::Tag s_SeriesDescriptionTag(0x0008, 0x103e);
46 static const ::gdcm::Tag s_PerformingPhysicianNameTag(0x0008, 0x1050);
47 static const ::gdcm::Tag s_SOPClassUIDTag(0x0008, 0x0016);
48 //Equipment
49 static const ::gdcm::Tag s_InstitutionNameTag(0x0008, 0x0080);
50 //Patient
51 static const ::gdcm::Tag s_PatientNameTag(0x0010, 0x0010);
52 static const ::gdcm::Tag s_PatientIDTag(0x0010, 0x0020);
53 static const ::gdcm::Tag s_PatientBirthDateTag(0x0010, 0x0030);
54 static const ::gdcm::Tag s_PatientSexTag(0x0010, 0x0040);
55 //Study
56 static const ::gdcm::Tag s_StudyInstanceUIDTag(0x0020, 0x000d);
57 static const ::gdcm::Tag s_StudyDateTag(0x0008, 0x0020);
58 static const ::gdcm::Tag s_StudyTimeTag(0x0008, 0x0030);
59 static const ::gdcm::Tag s_ReferringPhysicianNameTag(0x0008, 0x0090);
60 static const ::gdcm::Tag s_StudyDescriptionTag(0x0008, 0x1030);
61 static const ::gdcm::Tag s_PatientAgeTag(0x0010, 0x1010);
62 
63 //------------------------------------------------------------------------------
64 
65 std::string getStringValue(const ::gdcm::Scanner& scanner,
66  const std::string& filename,
67  const gdcm::Tag& tag)
68 {
69  std::string result = "";
70  const char* value = scanner.GetValue( filename.c_str(), tag );
71  if(value)
72  {
73  // Trim buffer
74  result = ::gdcm::LOComp::Trim(value);
75  }
76  return result;
77 }
78 
79 //------------------------------------------------------------------------------
80 
81 std::string getStringValue(const ::gdcm::DataSet& dataset,
82  const gdcm::Tag& tag)
83 {
84  std::string result = "";
85  if (dataset.FindDataElement(tag))
86  {
87  const ::gdcm::DataElement& dataElement = dataset.GetDataElement(tag);
88 
89  if (!dataElement.IsEmpty())
90  {
91  // Retrieve buffer
92  const ::gdcm::ByteValue* bv = dataElement.GetByteValue();
93  if(bv)
94  {
95  std::string buffer(bv->GetPointer(), bv->GetLength());
96  // Trim buffer
97  result = ::gdcm::LOComp::Trim(buffer.c_str());
98  }
99  }
100  }
101  return result;
102 }
103 
104 // ----------------------------------------------------------------------------
105 
107 {
108 }
109 
110 // ----------------------------------------------------------------------------
111 
113 {
114 }
115 
116 // ----------------------------------------------------------------------------
117 
118 DicomSeries::DicomSeriesContainerType DicomSeries::read(FilenameContainerType& filenames,
119  const SPTR(::fwJobs::Observer)& readerObserver,
120  const SPTR(::fwJobs::Observer)& completeSeriesObserver)
121 {
122  DicomSeriesContainerType seriesDB = DicomSeries::splitFiles(filenames, readerObserver);
123  DicomSeries::fillSeries(seriesDB, completeSeriesObserver);
124  return seriesDB;
125 }
126 
127 //------------------------------------------------------------------------------
128 
129 void DicomSeries::complete(DicomSeriesContainerType& seriesDB, const SPTR(::fwJobs::Observer)& completeSeriesObserver)
130 {
131  std::set< ::gdcm::Tag > selectedtags;
132  selectedtags.insert( s_SpecificCharacterSetTag);
133  selectedtags.insert( s_SeriesInstanceUIDTag);
134  selectedtags.insert( s_ModalityTag);
135  selectedtags.insert( s_SeriesDateTag);
136  selectedtags.insert( s_SeriesTimeTag);
137  selectedtags.insert( s_SeriesDescriptionTag);
138  selectedtags.insert( s_PerformingPhysicianNameTag);
139  selectedtags.insert( s_SOPClassUIDTag);
140 
141  for(const auto& series : seriesDB)
142  {
143  if(series->getDicomContainer().empty())
144  {
145  SLM_ERROR("DicomSeries doesn't not contain any instance.");
146  break;
147  }
148  const auto& firstItem = series->getDicomContainer().begin();
149  const ::fwMemory::BufferObject::sptr bufferObj = firstItem->second;
150  const ::fwMemory::BufferManager::StreamInfo streamInfo = bufferObj->getStreamInfo();
151  SPTR(std::istream) is = streamInfo.stream;
152 
153  ::gdcm::Reader reader;
154  reader.SetStream(*is);
155 
156  if(!reader.ReadSelectedTags(selectedtags))
157  {
158  FW_RAISE("Unable to read Dicom file '"<< bufferObj->getStreamInfo().fsFile.string() <<"' "<<
159  "(slice: '" << firstItem->first << "')");
160  }
161  const ::gdcm::DataSet& dataset = reader.GetFile().GetDataSet();
162 
163  //Modality
164  std::string modality = getStringValue( dataset, s_ModalityTag );
165  series->setModality(modality);
166 
167  //Date
168  std::string seriesDate = getStringValue( dataset, s_SeriesDateTag );
169  series->setDate(seriesDate);
170 
171  //Time
172  std::string seriesTime = getStringValue( dataset, s_SeriesTimeTag );
173  series->setTime(seriesTime);
174 
175  //Description
176  std::string seriesDescription = getStringValue( dataset, s_SeriesDescriptionTag );
177  series->setDescription(seriesDescription);
178 
179  //Performing Physicians Name
180  std::string performingPhysicianNamesStr = getStringValue( dataset, s_PerformingPhysicianNameTag );
181 
182  if(!performingPhysicianNamesStr.empty())
183  {
184  ::fwMedData::DicomValuesType performingPhysicianNames;
185  ::boost::split( performingPhysicianNames, performingPhysicianNamesStr, ::boost::is_any_of("\\"));
186  series->setPerformingPhysiciansName(performingPhysicianNames);
187  }
188 
189  // Add the SOPClassUID to the series
190  std::string sopClassUID = getStringValue( dataset, s_SOPClassUIDTag );
191  ::fwMedData::DicomSeries::SOPClassUIDContainerType sopClassUIDContainer = series->getSOPClassUIDs();
192  sopClassUIDContainer.insert(sopClassUID);
193  series->setSOPClassUIDs(sopClassUIDContainer);
194  }
195 
196  this->fillSeries(seriesDB, completeSeriesObserver);
197 }
198 
199 //------------------------------------------------------------------------------
200 
201 DicomSeries::DicomSeriesContainerType DicomSeries::splitFiles(FilenameContainerType& filenames,
202  const ::fwJobs::Observer::sptr& readerObserver)
203 {
204  ::gdcm::Scanner seriesScanner;
205  seriesScanner.AddTag(s_SpecificCharacterSetTag);
206  seriesScanner.AddTag(s_SeriesInstanceUIDTag);
207  seriesScanner.AddTag(s_ModalityTag);
208  seriesScanner.AddTag(s_SeriesDateTag);
209  seriesScanner.AddTag(s_SeriesTimeTag);
210  seriesScanner.AddTag(s_SeriesDescriptionTag);
211  seriesScanner.AddTag(s_PerformingPhysicianNameTag);
212  seriesScanner.AddTag(s_SOPClassUIDTag);
213  seriesScanner.AddTag(s_MediaStorageSOPClassUID);
214 
215  readerObserver->setTotalWorkUnits(filenames.size());
216  readerObserver->doneWork(0);
217 
218  std::vector< std::string > fileVec;
219  for(auto file : filenames)
220  {
221  fileVec.push_back(file.string());
222  }
223 
224  bool status = seriesScanner.Scan( fileVec );
225  FW_RAISE_IF("Unable to read the files.", !status);
226 
227  ::gdcm::Directory::FilenamesType keys = seriesScanner.GetKeys();
228  ::gdcm::Directory::FilenamesType::const_iterator it;
229 
230  unsigned int progress = 0;
231 
232  DicomSeriesContainerType seriesDB;
233 
234  //Loop through every files available in the scanner
235  for(const ::boost::filesystem::path& dicomFile : filenames)
236  {
237  auto filename = dicomFile.string();
238 
239  OSLM_ASSERT("The file \"" << dicomFile << "\" is not a key of the gdcm scanner",
240  seriesScanner.IsKey(filename.c_str()));
241 
242  const std::string sopClassUID = getStringValue(seriesScanner, filename, s_SOPClassUIDTag);
243  const std::string mediaStorageSopClassUID = getStringValue(seriesScanner, filename, s_MediaStorageSOPClassUID);
244 
245  if(sopClassUID != ::gdcm::MediaStorage::GetMSString(::gdcm::MediaStorage::MediaStorageDirectoryStorage)
246  && mediaStorageSopClassUID
247  != ::gdcm::MediaStorage::GetMSString(::gdcm::MediaStorage::MediaStorageDirectoryStorage))
248  {
249 
250  this->createSeries(seriesDB, seriesScanner, dicomFile);
251  }
252 
253  if (!readerObserver || readerObserver->cancelRequested())
254  {
255  break;
256  }
257 
258  readerObserver->doneWork(static_cast< std::uint64_t >(++progress * 100 / keys.size()));
259  }
260 
261  return seriesDB;
262 }
263 
264 //------------------------------------------------------------------------------
265 
266 void DicomSeries::fillSeries(DicomSeriesContainerType& seriesDB,
267  const ::fwJobs::Observer::sptr& completeSeriesObserver)
268 {
269  m_patientMap.clear();
270  m_studyMap.clear();
271  m_equipmentMap.clear();
272 
273  std::set< ::gdcm::Tag > selectedtags;
274  selectedtags.insert(s_SpecificCharacterSetTag);
275  selectedtags.insert(s_PatientIDTag);
276  selectedtags.insert(s_PatientNameTag);
277  selectedtags.insert(s_PatientBirthDateTag);
278  selectedtags.insert(s_PatientSexTag);
279  selectedtags.insert(s_StudyInstanceUIDTag);
280  selectedtags.insert(s_StudyDateTag);
281  selectedtags.insert(s_StudyTimeTag);
282  selectedtags.insert(s_ReferringPhysicianNameTag);
283  selectedtags.insert(s_StudyDescriptionTag);
284  selectedtags.insert(s_PatientAgeTag);
285  selectedtags.insert(s_InstitutionNameTag);
286  selectedtags.insert(s_SeriesInstanceUIDTag);
287 
288  std::uint64_t progress = 0;
289 
290  // Fill series
291  for(const ::fwMedData::DicomSeries::sptr& series : seriesDB)
292  {
293  // Compute number of instances
294  const size_t size = series->getDicomContainer().size();
295  series->setNumberOfInstances(size);
296 
297  if(!size)
298  {
299  SLM_ERROR("The DicomSeries doesn't contain any instance.");
300  break;
301  }
302 
303  // Load first instance
304  const auto& firstItem = series->getDicomContainer().begin();
305  const ::fwMemory::BufferObject::sptr bufferObj = firstItem->second;
306  const ::fwMemory::BufferManager::StreamInfo streamInfo = bufferObj->getStreamInfo();
307  SPTR(std::istream) is = streamInfo.stream;
308 
309  ::gdcm::Reader reader;
310  reader.SetStream(*is);
311 
312  if(!reader.ReadSelectedTags(selectedtags))
313  {
314  FW_RAISE("Unable to read Dicom file '"<< bufferObj->getStreamInfo().fsFile.string() <<"' "<<
315  "(slice: '" << firstItem->first << "')");
316  }
317  const ::gdcm::DataSet& dataset = reader.GetFile().GetDataSet();
318 
319  // Create data objects from first instance
320  ::fwMedData::Patient::sptr patient = this->createPatient(dataset);
321  ::fwMedData::Study::sptr study = this->createStudy(dataset);
322  ::fwMedData::Equipment::sptr equipment = this->createEquipment(dataset);
323 
324  // Fill series
325  series->setPatient(patient);
326  series->setStudy(study);
327  series->setEquipment(equipment);
328 
329  if(completeSeriesObserver)
330  {
331  completeSeriesObserver->doneWork(static_cast<std::uint64_t>(++progress * 100 / seriesDB.size() ));
332 
333  if(completeSeriesObserver->cancelRequested())
334  {
335  break;
336  }
337  }
338  }
339 }
340 
341 //------------------------------------------------------------------------------
342 
343 void DicomSeries::createSeries(DicomSeriesContainerType& seriesDB,
344  const ::gdcm::Scanner& scanner,
345  const ::boost::filesystem::path& filename)
346 {
347  ::fwMedData::DicomSeries::sptr series = ::fwMedData::DicomSeries::sptr();
348 
349  const std::string stringFilename = filename.string();
350 
351  // Get Series Instance UID
352  std::string seriesInstanceUID = getStringValue( scanner, stringFilename, s_SeriesInstanceUIDTag );
353 
354  // Check if the series already exists
355  for(::fwMedData::DicomSeries::sptr dicomSeries : seriesDB)
356  {
357  if(dicomSeries->getInstanceUID() == seriesInstanceUID)
358  {
359  series = dicomSeries;
360  break;
361  }
362  }
363 
364  // If the series doesn't exist we create it
365  if(!series)
366  {
367  series = ::fwMedData::DicomSeries::New();
368 
369  seriesDB.push_back(series);
370 
371  //Instance UID
372  series->setInstanceUID(seriesInstanceUID);
373 
374  //Modality
375  std::string modality = getStringValue( scanner, stringFilename, s_ModalityTag );
376  series->setModality(modality);
377 
378  //Date
379  std::string seriesDate = getStringValue( scanner, stringFilename, s_SeriesDateTag );
380  series->setDate(seriesDate);
381 
382  //Time
383  std::string seriesTime = getStringValue( scanner, stringFilename, s_SeriesTimeTag );
384  series->setTime(seriesTime);
385 
386  //Description
387  std::string seriesDescription = getStringValue( scanner, stringFilename, s_SeriesDescriptionTag );
388  series->setDescription(seriesDescription);
389 
390  //Performing Physicians Name
391  std::string performingPhysicianNamesStr = getStringValue( scanner, stringFilename,
392  s_PerformingPhysicianNameTag );
393 
394  if(!performingPhysicianNamesStr.empty())
395  {
396  ::fwMedData::DicomValuesType performingPhysicianNames;
397  ::boost::split( performingPhysicianNames, performingPhysicianNamesStr, ::boost::is_any_of("\\"));
398  series->setPerformingPhysiciansName(performingPhysicianNames);
399  }
400  }
401 
402  // Add the SOPClassUID to the series
403  std::string sopClassUID = getStringValue( scanner, stringFilename, s_SOPClassUIDTag );
404  ::fwMedData::DicomSeries::SOPClassUIDContainerType sopClassUIDContainer = series->getSOPClassUIDs();
405  sopClassUIDContainer.insert(sopClassUID);
406  series->setSOPClassUIDs(sopClassUIDContainer);
407 
408  // Add the instance to the series
409  const size_t instanceNumber = series->getDicomContainer().size();
410  series->addDicomPath(instanceNumber, filename);
411 }
412 
413 //------------------------------------------------------------------------------
414 
415 ::fwMedData::Patient::sptr DicomSeries::createPatient(const ::gdcm::DataSet& dataset)
416 {
417  ::fwMedData::Patient::sptr result;
418 
419  // Get Patient ID
420  std::string patientID = getStringValue( dataset, s_PatientIDTag );
421 
422  // Check if the patient already exists
423  if(m_patientMap.find(patientID) == m_patientMap.end())
424  {
425  result = ::fwMedData::Patient::New();
426  m_patientMap[patientID] = result;
427 
428  //Patient ID
429  result->setPatientId(patientID);
430 
431  //Patient Name
432  std::string patientName = getStringValue( dataset, s_PatientNameTag );
433  result->setName(patientName);
434 
435  //Patient Birthdate
436  std::string patientBirthDate = getStringValue( dataset, s_PatientBirthDateTag );
437  result->setBirthdate(patientBirthDate);
438 
439  //Patient Sex
440  std::string patientSex = getStringValue( dataset, s_PatientSexTag );
441  result->setSex(patientSex);
442 
443  }
444  else
445  {
446  result = ::fwMedData::Patient::New();
447  result->deepCopy(m_patientMap[patientID]);
448  }
449 
450  return result;
451 }
452 
453 //------------------------------------------------------------------------------
454 
455 ::fwMedData::Study::sptr DicomSeries::createStudy(const ::gdcm::DataSet& dataset)
456 {
457  ::fwMedData::Study::sptr result;
458 
459  // Get Study ID
460  std::string studyInstanceUID = getStringValue( dataset, s_StudyInstanceUIDTag );
461 
462  // Check if the study already exists
463  if(m_studyMap.find(studyInstanceUID) == m_studyMap.end())
464  {
465  result = ::fwMedData::Study::New();
466  m_studyMap[studyInstanceUID] = result;
467 
468  //Study ID
469  result->setInstanceUID(studyInstanceUID);
470 
471  //Study Date
472  std::string studyDate = getStringValue( dataset, s_StudyDateTag );
473  result->setDate(studyDate);
474 
475  //Study Time
476  std::string studyTime = getStringValue( dataset, s_StudyTimeTag );
477  result->setTime(studyTime);
478 
479  //Referring Physician Name
480  std::string referringPhysicianName = getStringValue( dataset, s_ReferringPhysicianNameTag );
481  result->setReferringPhysicianName(referringPhysicianName);
482 
483  //Study Description
484  std::string studyDescription = getStringValue( dataset, s_StudyDescriptionTag );
485  result->setDescription(studyDescription);
486 
487  //Study Patient Age
488  std::string patientAge = getStringValue( dataset, s_PatientAgeTag );
489  result->setPatientAge(patientAge);
490 
491  }
492  else
493  {
494  result = ::fwMedData::Study::New();
495  result->deepCopy(m_studyMap[studyInstanceUID]);
496  }
497 
498  return result;
499 }
500 
501 //------------------------------------------------------------------------------
502 
503 ::fwMedData::Equipment::sptr DicomSeries::createEquipment(const ::gdcm::DataSet& dataset)
504 {
505  ::fwMedData::Equipment::sptr result;
506 
507  // Get Institution Name
508  std::string institutionName = getStringValue( dataset, s_InstitutionNameTag );
509 
510  // Check if the equipment already exists
511  if(m_equipmentMap.find(institutionName) == m_equipmentMap.end())
512  {
513  result = ::fwMedData::Equipment::New();
514  m_equipmentMap[institutionName] = result;
515 
516  //Institution Name
517  result->setInstitutionName(institutionName);
518 
519  }
520  else
521  {
522  result = ::fwMedData::Equipment::New();
523  result->deepCopy(m_equipmentMap[institutionName]);
524  }
525 
526  return result;
527 }
528 
529 //------------------------------------------------------------------------------
530 
531 } //helper
532 } //fwGdcmIO
#define SPTR(_cls_)
#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
std::shared_ptr< ::fwMedData::Patient > createPatient(const ::gdcm::DataSet &dataset)
Create a patient from the dataset and store it in the patient map.
void fillSeries(DicomSeriesContainerType &seriesDB, const std::shared_ptr< ::fwJobs::Observer > &completeSeriesObserver)
Fill series with information contained in first instance.
void createSeries(DicomSeriesContainerType &seriesDB, const ::gdcm::Scanner &scanner, const ::boost::filesystem::path &filename)
Create a series from the dataset and store it in the series map.
The namespace fwGdcmIO contains reader, writer and helper for dicom data.
DicomSeriesContainerType splitFiles(FilenameContainerType &filenames, const std::shared_ptr< ::fwJobs::Observer > &readerObserver)
Create DicomSeries from list of files. Every instance is read in order to retrieve instance informati...
#define SLM_ERROR(message)
Definition: spyLog.hpp:272
std::shared_ptr< ::fwMedData::Equipment > createEquipment(const ::gdcm::DataSet &dataset)
Create an equipment from the dataset and store it in the equipment map.
std::shared_ptr< ::fwMedData::Study > createStudy(const ::gdcm::DataSet &dataset)
Create a study from the dataset and store it in the study map.
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.
FWGDCMIO_API void complete(DicomSeriesContainerType &seriesDB, const std::shared_ptr< ::fwJobs::Observer > &completeSeriesObserver)
Fill DicomSeries information for series generated using DICOMDIR helper.
This file defines SpyLog macros. These macros are used to log messages to a file or to the console du...
This class manages a job.
Definition: Observer.hpp:22