fw4spl
fwGdcmIO/src/fwGdcmIO/helper/DicomDir.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/DicomDir.hpp"
8 
9 #include "fwGdcmIO/helper/DicomDataReader.hxx"
10 
11 #include <fwCore/exceptionmacros.hpp>
12 #include <fwCore/spyLog.hpp>
13 
14 #include <fwJobs/IJob.hpp>
15 #include <fwJobs/Observer.hpp>
16 
17 #include <fwLog/Logger.hpp>
18 
19 #include <fwMedData/DicomSeries.hpp>
20 
21 #include <boost/algorithm/string.hpp>
22 #include <boost/filesystem/operations.hpp>
23 #include <boost/lexical_cast.hpp>
24 #include <boost/regex.h>
25 
26 #include <gdcmMediaStorage.h>
27 #include <gdcmReader.h>
28 
29 namespace fwGdcmIO
30 {
31 namespace helper
32 {
33 // ----------------------------------------------------------------------------
34 
35 ::boost::filesystem::path DicomDir::findDicomDir(const ::boost::filesystem::path& root)
36 {
37  ::boost::filesystem::path current = root;
38 
39  while(::boost::filesystem::exists(current))
40  {
41  ::boost::filesystem::path dicomDirPath = current / "dicomdir";
42  if(::boost::filesystem::exists(dicomDirPath) && !::boost::filesystem::is_directory(dicomDirPath))
43  {
44  return dicomDirPath;
45  }
46 
47  dicomDirPath = current / "DICOMDIR";
48  if(::boost::filesystem::exists(dicomDirPath) && !::boost::filesystem::is_directory(dicomDirPath))
49  {
50  return dicomDirPath;
51  }
52 
53  current = current.parent_path();
54  }
55 
56  return ::boost::filesystem::path();
57 
58 }
59 
60 // ----------------------------------------------------------------------------
61 
62 void processDirInformation(const ::boost::filesystem::path& dicomdir,
63  const ::boost::filesystem::path& rootDicomDirPath,
64  ::fwMedData::DicomSeries::sptr currentSeries,
65  std::map < std::string, ::fwMedData::DicomSeries::sptr >& dicomSeriesMap,
66  const ::fwLog::Logger::sptr& logger,
67  std::function< void(std::uint64_t) >& progress,
68  std::function< bool() >& cancel,
69  double& p,
70  double& ptotal)
71 {
72  SLM_ASSERT("You must specify a valid dicomdir.", ::boost::filesystem::exists(dicomdir)
73  && !::boost::filesystem::is_directory(dicomdir));
74 
75  // Try to read the file
76  ::gdcm::Reader reader;
77  reader.SetFileName(dicomdir.string().c_str());
78  if( !reader.Read() )
79  {
80  return;
81  }
82 
83  const ::gdcm::File& gdcmFile = reader.GetFile();
84  const ::gdcm::DataSet& dataset = gdcmFile.GetDataSet();
85 
86  // Check if the file is a DICOMDIR
87  ::gdcm::MediaStorage mediaStorage;
88  mediaStorage.SetFromFile(gdcmFile);
89  if(mediaStorage != ::gdcm::MediaStorage::MediaStorageDirectoryStorage )
90  {
91  SLM_ERROR("This file is not a DICOMDIR");
92  return;
93  }
94 
95  // Check the MediaStorageSOPClass
96  const ::gdcm::FileMetaInformation& fileMetaInformation = gdcmFile.GetHeader();
97  const std::string& mediaStorageSOP =
98  ::fwGdcmIO::helper::DicomDataReader::getTagValue< 0x0002, 0x0002 >(fileMetaInformation);
99 
100  if (mediaStorageSOP != ::gdcm::MediaStorage::GetMSString(::gdcm::MediaStorage::MediaStorageDirectoryStorage))
101  {
102  SLM_ERROR("This file is not a DICOMDIR");
103  return;
104  }
105 
106  // For each root elements
107  typedef std::set<gdcm::DataElement> DataElementSet;
108  typedef DataElementSet::const_iterator ConstIterator;
109 
110  for(ConstIterator it = dataset.GetDES().begin(); it != dataset.GetDES().end(); ++it)
111  {
112  // Directory Record Sequence
113  if (it->GetTag() == ::gdcm::Tag(0x0004, 0x1220))
114  {
115  ::gdcm::SmartPointer< ::gdcm::SequenceOfItems > sequence = it->GetValueAsSQ();
116  ptotal += static_cast<double>(sequence->GetNumberOfItems());
117 
118  for(unsigned int index = 1; index <= sequence->GetNumberOfItems(); ++index)
119  {
120  // Retrieve item
121  ::gdcm::Item& item = sequence->GetItem(index);
122 
123  // Directory Record Type
124  const std::string recordType =
125  ::fwGdcmIO::helper::DicomDataReader::getTagValue< 0x0004, 0x1430 >(item.GetNestedDataSet());
126 
127  // Check Referenced File ID
128  std::string refFileID =
129  ::fwGdcmIO::helper::DicomDataReader::getTagValue< 0x0004, 0x1500 >(item.GetNestedDataSet());
130 
131  if(recordType == "IMAGE")
132  {
133  // Read file path
134  std::string file = ::fwGdcmIO::helper::DicomDataReader::getTagValue< 0x0004, 0x1500 >(
135  item.GetNestedDataSet());
136  std::replace( file.begin(), file.end(), '\\', '/');
137  SLM_WARN_IF("Dicom instance doesn't have a referenced file id.", file.empty());
138 
139  const ::boost::filesystem::path path = rootDicomDirPath / file;
140  OSLM_WARN_IF("Unable to find path :" << path, !::boost::filesystem::exists(path));
141  OSLM_WARN_IF("Dicomdir is badly formatted. Skipping path :" << path, !currentSeries);
142 
143  if(!currentSeries || file.empty())
144  {
145  logger->warning("DICOMDIR file is badly formatted. Instances may be missing");
146  }
147  else if(::boost::filesystem::exists(path))
148  {
149  auto instanceNumber = ::boost::lexical_cast<unsigned int>(
151  0x0013 >(item.GetNestedDataSet()));
152  currentSeries->addDicomPath(instanceNumber, path);
153  }
154  else
155  {
156  logger->warning("Unable to retrieve file :" + path.string());
157  }
158  }
159  else
160  {
161  // If the record is a series, we select the current series
162  if(recordType == "SERIES")
163  {
164  const std::string& seriesUID =
165  ::fwGdcmIO::helper::DicomDataReader::getTagValue< 0x0020, 0x000e >(item.GetNestedDataSet());
166  if(dicomSeriesMap.find(seriesUID) == dicomSeriesMap.end())
167  {
168  ::fwMedData::DicomSeries::sptr series = ::fwMedData::DicomSeries::New();
169  series->setInstanceUID(seriesUID);
170  dicomSeriesMap[seriesUID] = series;
171  }
172 
173  currentSeries = dicomSeriesMap[seriesUID];
174  }
175 
176  std::replace(refFileID.begin(), refFileID.end(), '\\', '/');
177  auto refFilePath = dicomdir.parent_path() / refFileID;
178  if(refFileID != "" && ::boost::filesystem::exists(refFilePath))
179  {
180  processDirInformation(refFilePath, rootDicomDirPath, currentSeries,
181  dicomSeriesMap, logger, progress, cancel, p, ptotal);
182  }
183  }
184 
185  if(progress)
186  {
187  progress(++p);
188  }
189 
190  if( cancel && cancel() )
191  {
192  break;
193  }
194 
195  }
196  }
197  }
198 }
199 
200 // ----------------------------------------------------------------------------
201 
202 void DicomDir::retrieveDicomSeries(const ::boost::filesystem::path& dicomdir,
203  std::vector< SPTR(::fwMedData::DicomSeries) >& seriesDB,
204  const ::fwLog::Logger::sptr& logger,
205  std::function< void(std::uint64_t) > progress,
206  std::function< bool() > cancel)
207 {
208  SLM_ASSERT("You must specify a valid dicomdir.", ::boost::filesystem::exists(dicomdir)
209  && !::boost::filesystem::is_directory(dicomdir));
210 
211  // Try to read the file
212  ::gdcm::Reader reader;
213  reader.SetFileName(dicomdir.string().c_str());
214  if( !reader.Read() )
215  {
216  return;
217  }
218 
219  const ::gdcm::File& gdcmFile = reader.GetFile();
220  const ::gdcm::DataSet& dataset = gdcmFile.GetDataSet();
221 
222  // Check if the file is a DICOMDIR
223  ::gdcm::MediaStorage mediaStorage;
224  mediaStorage.SetFromFile(gdcmFile);
225  if(mediaStorage != ::gdcm::MediaStorage::MediaStorageDirectoryStorage )
226  {
227  SLM_ERROR("This file is not a DICOMDIR");
228  return;
229  }
230 
231  // Check the MediaStorageSOPClass
232  const ::gdcm::FileMetaInformation& fileMetaInformation = gdcmFile.GetHeader();
233  const std::string& mediaStorageSOP =
234  ::fwGdcmIO::helper::DicomDataReader::getTagValue< 0x0002, 0x0002 >(fileMetaInformation);
235 
236  if (mediaStorageSOP != ::gdcm::MediaStorage::GetMSString(::gdcm::MediaStorage::MediaStorageDirectoryStorage))
237  {
238  SLM_ERROR("This file is not a DICOMDIR");
239  return;
240  }
241 
242  // For each root elements
243  typedef std::set<gdcm::DataElement> DataElementSet;
244  typedef DataElementSet::const_iterator ConstIterator;
245 
246  double p = 0.;
247  double ptotal = 0.;
248 
249  if(progress)
250  {
251  // Compute total progress
252  for(ConstIterator it = dataset.GetDES().begin(); it != dataset.GetDES().end(); ++it)
253  {
254  // Directory Record Sequence
255  if (it->GetTag() == ::gdcm::Tag(0x0004, 0x1220))
256  {
257  ::gdcm::SmartPointer< ::gdcm::SequenceOfItems > sequence = it->GetValueAsSQ();
258 
259  ptotal += static_cast<double>(sequence->GetNumberOfItems());
260  }
261  }
262  }
263 
264  if( 0. == ptotal )
265  {
266  ptotal = 1.;
267  }
268 
269  std::map < std::string, ::fwMedData::DicomSeries::sptr > dicomSeriesMap;
270  processDirInformation(dicomdir, dicomdir.parent_path(), nullptr, dicomSeriesMap,
271  logger, progress, cancel, p, ptotal);
272 
273  if( cancel && cancel() )
274  {
275  return;
276  }
277 
278  for(auto entry : dicomSeriesMap)
279  {
280  auto series = entry.second;
281  const size_t size = series->getDicomContainer().size();
282  if(size)
283  {
284  series->setNumberOfInstances(size);
285  seriesDB.push_back(series);
286  }
287  else
288  {
289  logger->critical("Unable to retrieve instances for this series : " + series->getInstanceUID());
290  }
291  }
292 }
293 
294 // ----------------------------------------------------------------------------
295 
296 } //helper
297 } //fwGdcmIO
#define SPTR(_cls_)
static std::string getTagValue(const ::gdcm::DataSet &dataset, const std::string &charset="", const ::fwLog::Logger::sptr &logger=nullptr)
Return a string from a tag found in dataset. An empty string returned means the tag is not found or e...
static FWGDCMIO_API::boost::filesystem::path findDicomDir(const ::boost::filesystem::path &root)
Find the DICOMDIR file in the parent arborescence.
The namespace fwGdcmIO contains reader, writer and helper for dicom data.
#define SLM_ERROR(message)
Definition: spyLog.hpp:272
#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
#define OSLM_WARN_IF(message, cond)
Definition: spyLog.hpp:267
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.
This file defines SpyLog macros. These macros are used to log messages to a file or to the console du...
#define SLM_WARN_IF(message, cond)
Definition: spyLog.hpp:265