fw4spl
vtkGdcmIO/src/vtkGdcmIO/SeriesDBReader.cpp
1 /* ***** BEGIN LICENSE BLOCK *****
2  * FW4SPL - Copyright (C) IRCAD, 2009-2017.
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 "vtkGdcmIO/SeriesDBReader.hpp"
8 
9 #include "vtkGdcmIO/helper/GdcmHelper.hpp"
10 
11 #include <fwCore/base.hpp>
12 
13 #include <fwData/Image.hpp>
14 
15 #include <fwDataIO/reader/registry/macros.hpp>
16 
17 #include <fwJobs/IJob.hpp>
18 #include <fwJobs/Observer.hpp>
19 
20 #include <fwMedData/Equipment.hpp>
21 #include <fwMedData/ImageSeries.hpp>
22 #include <fwMedData/Patient.hpp>
23 #include <fwMedData/Series.hpp>
24 #include <fwMedData/SeriesDB.hpp>
25 #include <fwMedData/Study.hpp>
26 
27 #include <fwTools/dateAndTime.hpp>
28 #include <fwTools/fromIsoExtendedString.hpp>
29 
30 #include <fwVtkIO/helper/vtkLambdaCommand.hpp>
31 #include <fwVtkIO/vtk.hpp>
32 
33 #include <boost/algorithm/string/classification.hpp>
34 #include <boost/algorithm/string/split.hpp>
35 #include <boost/filesystem/path.hpp>
36 
37 #include <gdcmAttribute.h>
38 #include <gdcmDataSet.h>
39 #include <gdcmImageHelper.h>
40 #include <gdcmIPPSorter.h>
41 #include <gdcmReader.h>
42 #include <gdcmScanner.h>
43 #include <gdcmSorter.h>
44 #include <vtkGDCMImageReader.h>
45 #include <vtkImageData.h>
46 #include <vtkImageWriter.h>
47 #include <vtkMedicalImageProperties.h>
48 #include <vtkSmartPointer.h>
49 #include <vtkStringArray.h>
50 
51 #include <exception>
52 
53 fwDataIOReaderRegisterMacro( ::vtkGdcmIO::SeriesDBReader );
54 
55 namespace vtkGdcmIO
56 {
57 
58 //------------------------------------------------------------------------------
59 
60 SeriesDBReader::SeriesDBReader(::fwDataIO::reader::IObjectReader::Key key) :
61  ::fwData::location::enableFolder< IObjectReader >(this),
62  ::fwData::location::enableMultiFiles< IObjectReader >(this),
63  m_job(::fwJobs::Observer::New("SeriesDB reader"))
64 {
66 }
67 
68 //------------------------------------------------------------------------------
69 
71 {
73 }
74 
75 //------------------------------------------------------------------------------
76 
77 ::fwMedData::SeriesDB::sptr SeriesDBReader::createSeriesDB( const ::boost::filesystem::path& dicomDir )
78 {
80  ::fwMedData::SeriesDB::sptr seriesDB = this->getConcreteObject();
81 
82  std::vector<std::string> filenames;
83  ::vtkGdcmIO::helper::DicomSearch::searchRecursivelyFiles(dicomDir, filenames);
84 
85  this->addSeries( seriesDB, filenames);
86  return seriesDB;
87 }
88 
89 //------------------------------------------------------------------------------
90 
91 // Define a custom sorter based on the InstanceNumber DICOM tag.
92 bool sortByInstanceNumber(const ::gdcm::DataSet& ds1, const ::gdcm::DataSet& ds2 )
93 {
94  ::gdcm::Attribute<0x0020, 0x0013> at1;
95  at1.Set( ds1 );
96  ::gdcm::Attribute<0x0020, 0x0013> at2;
97  at2.Set( ds2 );
98  return at1 < at2;
99 }
100 
101 //----------------------------------------------------------------------------------------
102 
103 void SeriesDBReader::addSeries( const ::fwMedData::SeriesDB::sptr& seriesDB,
104  const std::vector< std::string >& filenames)
105 {
106  //gdcm::Trace::SetDebug( 1 );
107  //gdcm::Trace::SetWarning( 1 );
108  //gdcm::Trace::SetError( 1 );
109 
110  ::gdcm::Scanner scanner;
111  const ::gdcm::Tag seriesUIDTag(0x0020, 0x000e);
112  const ::gdcm::Tag seriesDateTag(0x0008, 0x0021);
113  const ::gdcm::Tag seriesTimeTag(0x0008, 0x0031);
114  const ::gdcm::Tag seriesTypeTag(0x0008, 0x0060);
115  const ::gdcm::Tag seriesDescriptionTag(0x0008, 0x103e);
116  const ::gdcm::Tag seriesPhysicianNamesTag(0x0008, 0x1050);
117 
118  const ::gdcm::Tag equipmentInstitutionNameTag(0x0008, 0x0080);
119 
120  const ::gdcm::Tag patientNameTag(0x0010, 0x0010);
121  const ::gdcm::Tag patientIDTag(0x0010, 0x0020);
122  const ::gdcm::Tag patientBirthdateTag(0x0010, 0x0030);
123  const ::gdcm::Tag patientSexTag(0x0010, 0x0040);
124  const ::gdcm::Tag studyUIDTag(0x0020, 0x000d);
125  const ::gdcm::Tag studyDateTag(0x0008, 0x0020);
126  const ::gdcm::Tag studyTimeTag(0x0008, 0x0030);
127  const ::gdcm::Tag studyReferingPhysicianNameTag(0x0008, 0x0090);
128  const ::gdcm::Tag studyDescriptionTag(0x0008, 0x1030);
129  const ::gdcm::Tag studyPatientAgeTag(0x0010, 0x1010);
130 
131  const ::gdcm::Tag imageTypeTag(0x0008, 0x0008);
132  const ::gdcm::Tag acquisitionDateTag(0x0008, 0x0022);
133  const ::gdcm::Tag acquisitionTimeTag(0x0008, 0x0032);
134 
135  scanner.AddTag( seriesUIDTag );
136  scanner.AddTag( seriesDateTag );
137  scanner.AddTag( seriesTimeTag );
138  scanner.AddTag( seriesTypeTag );
139  scanner.AddTag( seriesDescriptionTag );
140  scanner.AddTag( seriesPhysicianNamesTag );
141  scanner.AddTag( equipmentInstitutionNameTag );
142  scanner.AddTag( studyUIDTag );
143  scanner.AddTag( patientNameTag );
144  scanner.AddTag( patientIDTag );
145  scanner.AddTag( patientBirthdateTag );
146  scanner.AddTag( patientSexTag );
147  scanner.AddTag( studyUIDTag );
148  scanner.AddTag( studyDateTag );
149  scanner.AddTag( studyTimeTag );
150  scanner.AddTag( studyReferingPhysicianNameTag );
151  scanner.AddTag( studyDescriptionTag );
152  scanner.AddTag( studyPatientAgeTag );
153  scanner.AddTag( imageTypeTag );
154  scanner.AddTag( acquisitionDateTag );
155  scanner.AddTag( acquisitionTimeTag );
156 
157  try
158  {
159  const bool isScanned = scanner.Scan( filenames );
160  if( !isScanned )
161  {
162  SLM_ERROR("Scanner failed");
163  return;
164  }
165  const ::gdcm::Directory::FilenamesType keys = scanner.GetKeys();
166 
167  typedef std::map< std::string, std::vector< std::string > > MapSeriesType;
168  MapSeriesType mapSeries;
169 
170  for(const std::string& filename : keys)
171  {
172  SLM_ASSERT("'"+filename+"' is not a key in the mapping table", scanner.IsKey(filename.c_str()));
173 
174  const char* seriesUID = scanner.GetValue( filename.c_str(), seriesUIDTag );
175  const char* acqDate = scanner.GetValue( filename.c_str(), acquisitionDateTag );
176 
177  if (seriesUID)
178  {
179  std::string fileSetId = seriesUID;
180 
181  if (acqDate)
182  {
183  fileSetId += "_";
184  fileSetId += acqDate;
185  }
186 
187  const char* imageType = scanner.GetValue(filename.c_str(), imageTypeTag);
188  if(imageType)
189  {
190  // Treatment of secondary capture dicom file.
191  SLM_TRACE("Image Type : " + std::string(imageType));
192  fileSetId += "_";
193  fileSetId += imageType;
194  }
195  mapSeries[fileSetId].push_back(filename);
196  }
197  else
198  {
199  SLM_ERROR("No series name found in : " + filename );
200  }
201  }
202 
203  for(const auto& elt : mapSeries)
204  {
205  ::fwMedData::ImageSeries::sptr series = ::fwMedData::ImageSeries::New();
206  ::fwMedData::Patient::sptr patient = series->getPatient();
207  ::fwMedData::Study::sptr study = series->getStudy();
208  ::fwMedData::Equipment::sptr equipment = series->getEquipment();
209 
210  seriesDB->getContainer().push_back(series);
211 
212  SLM_TRACE( "Processing: '" + elt.first + "' file set.");
213  const MapSeriesType::mapped_type& files = elt.second;
214  if ( !files.empty() )
215  {
216  vtkSmartPointer< vtkStringArray > fileArray = vtkSmartPointer< vtkStringArray >::New();
217  vtkSmartPointer< vtkGDCMImageReader > reader = vtkSmartPointer< vtkGDCMImageReader >::New();
218  reader->FileLowerLeftOn();
219 
220  ::gdcm::IPPSorter ippSorter;
221  ippSorter.SetComputeZSpacing( true );
222  ippSorter.SetZSpacingTolerance( 1e-3 );
223  bool isSorted = ippSorter.Sort( files );
224 
225  std::vector<std::string> sorted;
226  double zspacing = 0.;
227  if(isSorted)
228  {
229  sorted = ippSorter.GetFilenames();
230  zspacing = ippSorter.GetZSpacing();
231  OSLM_TRACE("Found z-spacing:" << ippSorter.GetZSpacing());
232  }
233  else
234  {
235  // Else an error has been encountered.
236  // We fall back to a more trivial sorting based on the InstanceNumber DICOM tag.
237  SLM_WARN("IPP Sorting failed. Falling back to Instance Number sorting.");
238  ::gdcm::Sorter sorter;
239  sorter.SetSortFunction(sortByInstanceNumber);
240  isSorted = sorter.StableSort( filenames);
241  if(isSorted)
242  {
243  // If the custom sorted returns true, it worked
244  // and the filenames are sorted by InstanceNumber (ASC).
245  sorted = sorter.GetFilenames();
246  }
247  else
248  {
249  // There is nothing more we can do to sort DICOM files.
250  SLM_ERROR("Failed to sort '"+elt.first+"'");
251  }
252  }
253 
254  fileArray->Initialize();
255  if(isSorted)
256  {
257  SLM_TRACE("Success to sort '" + elt.first+"'");
258  if (!zspacing && sorted.size() > 1)
259  {
260  SLM_TRACE( "Guessing zspacing ..." );
261  if (!sorted.empty())
262  {
263  ::gdcm::Reader localReader1;
264  ::gdcm::Reader localReader2;
265  const std::string& f1 = *(sorted.begin());
266  const std::string& f2 = *(sorted.begin() + 1);
267  SLM_TRACE( "Search spacing in: '" + f1 +"'");
268  SLM_TRACE( "Search spacing in: '" + f2 +"'");
269 
270  localReader1.SetFileName( f1.c_str() );
271  localReader2.SetFileName( f2.c_str() );
272  const bool canRead = localReader1.Read() && localReader2.Read();
273  if(canRead)
274  {
275  const std::vector<double> vOrigin1 =
276  ::gdcm::ImageHelper::GetOriginValue(localReader1.GetFile());
277  const std::vector<double> vOrigin2 =
278  ::gdcm::ImageHelper::GetOriginValue(localReader2.GetFile());
279  zspacing = vOrigin2[2] - vOrigin1[2];
280  OSLM_TRACE(
281  "Found z-spacing:" << zspacing << " from : << " << vOrigin2[2] << " | " <<
282  vOrigin1[2]);
283  }
284  SLM_ERROR_IF("Cannot read: '" + f1 + "' or: '" + f2 +"'", !canRead);
285  }
286  }
287  }
288 
289  for(const std::string& file : sorted)
290  {
291  SLM_TRACE("Add '" + file + "' to vtkGdcmReader");
292  fileArray->InsertNextValue(file.c_str());
293  }
294 
295  ::fwData::Image::sptr pDataImage = ::fwData::Image::New();
296  bool res = false;
297  if (fileArray->GetNumberOfValues() > 0)
298  {
299  reader->SetFileNames( fileArray );
300  try
301  {
302  SLM_TRACE("Read Series: '" + elt.first + "'");
303 
304  //add progress observation
305  vtkSmartPointer< ::fwVtkIO::helper::vtkLambdaCommand > progressCallback =
306  vtkSmartPointer< ::fwVtkIO::helper::vtkLambdaCommand >::New();
307  progressCallback->SetCallback([this](vtkObject* caller, long unsigned int, void* )
308  {
309  auto filter = static_cast<vtkGDCMImageReader*>(caller);
310  m_job->doneWork( filter->GetProgress()*100 );
311  });
312  reader->AddObserver(vtkCommand::ProgressEvent, progressCallback);
313 
314  m_job->addSimpleCancelHook( [&]()
315  {
316  reader->AbortExecuteOn();
317  } );
318 
319  reader->Update();
320  reader->UpdateInformation();
321  reader->PropagateUpdateExtent();
322  try
323  {
324  ::fwVtkIO::fromVTKImage(reader->GetOutput(), pDataImage);
325  res = true;
326  }
327  catch(std::exception& e)
328  {
329  OSLM_ERROR("VTKImage to fwData::Image failed : "<<e.what());
330  }
331  }
332  catch (std::exception& e)
333  {
334  OSLM_ERROR( "Error during conversion : " << e.what() );
335  }
336  catch (...)
337  {
338  SLM_ERROR( "Unexpected error during conversion" );
339  }
340  m_job->finish();
341  }
342 
343  if (res)
344  {
345 
346  SLM_ASSERT("No file to read", !files.empty());
347 
348  // Read medical info
349  vtkMedicalImageProperties* medprop = reader->GetMedicalImageProperties();
350 
351  const std::string patientName = medprop->GetPatientName(); //"0010|0010"
352  const std::string patientId = medprop->GetPatientID();
353  const std::string patientBirthdate = medprop->GetPatientBirthDate(); //"0010|0030"
354  const std::string patientSex = medprop->GetPatientSex(); //"0010|0040"
355 
356  const ::gdcm::Scanner::ValuesType gdcmPhysicianNames = scanner.GetValues( seriesPhysicianNamesTag );
357 
358  const char* seriesUIDStr = scanner.GetValue( files[0].c_str(), seriesUIDTag );
359  const char* seriesTimeStr = scanner.GetValue( files[0].c_str(), seriesTimeTag );
360  const char* seriesDateStr = scanner.GetValue( files[0].c_str(), seriesDateTag );
361 
362  const std::string seriesModality = medprop->GetModality(); //"0008|0060"
363  const std::string seriesDescription = medprop->GetSeriesDescription();
364  const std::string seriesDate = ( seriesDateStr ? seriesDateStr : "" );
365  const std::string seriesTime = ( seriesTimeStr ? seriesTimeStr : "" );
366 
367  ::fwMedData::DicomValuesType seriesPhysicianNames;
368  for(const std::string& name : gdcmPhysicianNames)
369  {
370  ::fwMedData::DicomValuesType result;
371  ::boost::split( result, name, ::boost::is_any_of("\\"));
372  seriesPhysicianNames.reserve(seriesPhysicianNames.size() + result.size());
373  seriesPhysicianNames.insert(seriesPhysicianNames.end(), result.begin(), result.end());
374  }
375 
376  const char* studyUIDStr = scanner.GetValue( files[0].c_str(), studyUIDTag );
377  const char* studyReferingPhysicianNameStr =
378  scanner.GetValue( files[0].c_str(), studyReferingPhysicianNameTag );
379 
380  const std::string studyDate = medprop->GetStudyDate();
381  const std::string studyTime = medprop->GetStudyTime();
382  const std::string studyDescription = medprop->GetStudyDescription(); //"0008|1030"
383  const std::string studyPatientAge = medprop->GetPatientAge();
384  const std::string equipementInstitution = medprop->GetInstitutionName(); //"0008|0080"
385 
386  const std::string studyReferingPhysicianName =
387  ( studyReferingPhysicianNameStr ? studyReferingPhysicianNameStr : "" );
388 
389  const double thickness = medprop->GetSliceThicknessAsDouble();//"0018|0050"
390  double center = 0.0;
391  double width = 0.0;
392  if (medprop->GetNumberOfWindowLevelPresets())//FIXME : Multiple preset !!!
393  {
394  medprop->GetNthWindowLevelPreset(0, &width, &center); //0028|1050,1051
395  }
396 
397  // Image must have 3 dimensions
398  if(pDataImage->getNumberOfDimensions() == 2)
399  {
400  ::fwData::Image::SizeType imgSize = pDataImage->getSize();
401  imgSize.resize(3);
402  imgSize[2] = 1;
403  pDataImage->setSize(imgSize);
404 
405  ::fwData::Image::OriginType imgOrigin = pDataImage->getOrigin();
406  imgOrigin.resize(3);
407  imgOrigin[2] = 0.;
408  pDataImage->setOrigin(imgOrigin);
409  }
410 
411  ::fwData::Image::SpacingType vPixelSpacing = pDataImage->getSpacing();
412  vPixelSpacing.resize(3);
413  // assume z-spacing = 1 if not guessed
414  vPixelSpacing[2] = zspacing ? zspacing : (thickness ? thickness : 1.);
415  pDataImage->setSpacing(vPixelSpacing);
416  pDataImage->setWindowCenter(center);
417  pDataImage->setWindowWidth(width);
418 
419  // Get the series instance UID.
420  SLM_ASSERT("No series UID", seriesUIDStr);
421  series->setInstanceUID(( seriesUIDStr ? seriesUIDStr : "UNKNOWN-UID" ));
422  series->setModality( seriesModality );
423  series->setDescription( seriesDescription );
424  series->setDate( seriesDate );
425  series->setTime( seriesTime );
426  series->setPerformingPhysiciansName( seriesPhysicianNames );
427  series->setImage(pDataImage);
428 
429  SLM_ASSERT("No study UID", studyUIDStr);
430  study->setInstanceUID(( studyUIDStr ? studyUIDStr : "UNKNOWN-UID" ));
431  study->setDate(studyDate);
432  study->setTime(studyTime);
433  study->setDescription(studyDescription);
434  study->setPatientAge(studyPatientAge);
435  study->setReferringPhysicianName(studyReferingPhysicianName);
436 
437  patient->setName(patientName);
438  patient->setPatientId(patientId);
439  patient->setBirthdate(patientBirthdate);
440  patient->setSex(patientSex);
441 
442  equipment->setInstitutionName(equipementInstitution);
443  } // if res
444  } // if !files.empty()
445  }
446  } // try
447  catch (std::exception& e)
448  {
449  OSLM_ERROR( "Try with another reader or retry with this reader on a specific subfolder : " << e.what() );
450  for(const auto filename : filenames)
451  {
452  SLM_ERROR("file error : " + filename );
453  }
454  }
455 }
456 
457 //------------------------------------------------------------------------------
458 
460 {
461  SLM_TRACE_FUNC();
462  ::fwMedData::SeriesDB::sptr seriesDB = this->getConcreteObject();
463  std::vector<std::string> filenames;
464  if(::fwData::location::have < ::fwData::location::Folder, ::fwDataIO::reader::IObjectReader > (this))
465  {
466  ::vtkGdcmIO::helper::DicomSearch::searchRecursivelyFiles(this->getFolder(), filenames);
467  }
468  else if(::fwData::location::have < ::fwData::location::MultiFiles, ::fwDataIO::reader::IObjectReader > (this))
469  {
470  for(::boost::filesystem::path file : this->getFiles())
471  {
472  filenames.push_back(file.string());
473  }
474  }
475  this->addSeries( seriesDB, filenames);
476 }
477 
478 //------------------------------------------------------------------------------
479 
481 {
482  return m_job;
483 }
484 
485 } //namespace vtkGdcmIO
486 
FWVTKIO_API void read() override
Reading operator.
#define SLM_TRACE_FUNC()
Trace contextual function signature.
Definition: spyLog.hpp:329
Key class used to restrict access to Object construction. See http://www.drdobbs.com/184402053.
#define OSLM_TRACE(message)
Definition: spyLog.hpp:230
vtkmGdcm reader/writer lib
Definition: GdcmHelper.hpp:15
std::shared_ptr< ::fwJobs::IJob > sptr
Cancel request callback type.
Definition: IJob.hpp:54
Reads DICOM data from a directory path in order to create a SeriesDB object.
#define SLM_WARN(message)
Definition: spyLog.hpp:261
#define SLM_ERROR(message)
Definition: spyLog.hpp:272
#define OSLM_ERROR(message)
Definition: spyLog.hpp:274
#define SLM_ERROR_IF(message, cond)
Definition: spyLog.hpp:276
#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
virtual std::shared_ptr< DataType > getConcreteObject()
m_object getter.
std::vector< double > SpacingType
Image spacing type.
#define SLM_TRACE(message)
Definition: spyLog.hpp:228
ILocation::VectPathType getFiles()
Get file system paths.
Definition: MultiFiles.hpp:77
FWVTKIO_API std::shared_ptr< ::fwJobs::IJob > getJob() const override
Contains the representation of the data objects used in the framework.
This namespace fwJobs provides jobs management.
::fwData::Array::SizeType SizeType
Image size type.
std::vector< double > OriginType
Image origin type.