CustusX  2023.01.05-dev+develop.0da12
An IGT application
cxDICOMReader.cpp
Go to the documentation of this file.
1 /*=========================================================================
2 This file is part of CustusX, an Image Guided Therapy Application.
3 
4 Copyright (c) SINTEF Department of Medical Technology.
5 All rights reserved.
6 
7 CustusX is released under a BSD 3-Clause license.
8 
9 See Lisence.txt (https://github.com/SINTEFMedtek/CustusX/blob/master/License.txt) for details.
10 =========================================================================*/
11 
12 #include "cxDICOMReader.h"
13 
14 #include <QDir>
15 #include <QProgressDialog>
16 #include <vtkImageData.h>
17 #include <ctkDICOMDatabase.h>
18 #include <ctkDICOMIndexer.h>
19 #include "cxImage.h"
20 #include "cxPatientModelService.h"
21 #include "cxLogger.h"
22 #include "cxDicomConverter.h"
23 #include "cxDicomImageReader.h"
24 #include "cxReporter.h"
25 
26 //dcmtk includes
27 #include "dcmtk/config/osconfig.h"
28 #include "dcmtk/oflog/nullap.h"
29 
30 namespace cx
31 {
33  FileReaderWriterImplService("DICOMReader" ,Image::getTypeName(), "", "dcm *", patientModelService)
34 {
35  this->stopDCMTKMessages();
36 }
37 
38 // This function is used for checking all readers if they can read a file,
39 // so output should only be used for debug.
40 bool DICOMReader::canRead(const QString &type, const QString &filename)
41 {
42  if(QFileInfo(filename).isDir())
43  return this->canReadDir(filename, true);
44 
45  return this->canReadFile(filename);
46 }
47 
48 bool DICOMReader::canReadDir(QString dirname, bool checkSubDirs)
49 {
50  QDir dir(dirname);
51  QStringList files = dir.entryList(QDir::NoDotAndDotDot | QDir::AllEntries);
52  for(int i = 0; i < files.size(); ++i)
53  {
54  QString fullPath = dirname+"/"+files[i];
55  if(this->canReadFile(fullPath))
56  return true;
57  else if(checkSubDirs && QFileInfo(fullPath).isDir())
58  {
59  if(this->canReadDir(fullPath, checkSubDirs))
60  return true;
61  }
62  }
63  return false;
64 }
65 
66 bool DICOMReader::canReadFile(QString filename)
67 {
68  if(QFileInfo(filename).isDir())
69  {
70  return false; //Don't look into subdirs
71  }
72 
73  QFile file(filename);
74  bool opened = file.open(QIODevice::ReadOnly);
75  if(!opened)
76  return false;
77 
78  // A DICOM file should have the characters "DICM" at position 0x80
79  bool success = file.seek(0x80);
80  if(!success)
81  {
82  //CX_LOG_WARNING() << "DICOMReader: File isn't large enough to be DICOM: " << filename;
83  return false;
84  }
85 
86  char buf[4];
87  int numReadChar = file.peek(buf, sizeof(buf));
88  if (numReadChar != sizeof(buf))
89  {
90  //CX_LOG_WARNING() << "DICOMReader: Cannot read from file: " << filename;
91  return false;
92  }
93  if (buf[0] != 'D' || buf[1] != 'I' || buf[2] != 'C' || buf[3] != 'M')
94  {
95  //CX_LOG_WARNING() << "DICOMReader: File isn't DICOM: " << filename;
96  return false;
97  }
98  else
99  return true;
100 }
101 
103 {
104  return Image::getTypeName();
105 }
106 
107 bool DICOMReader::readInto(DataPtr data, QString filename)
108 {
109  return this->readInto(boost::dynamic_pointer_cast<Image>(data), filename);
110 }
111 
112 bool DICOMReader::readInto(ImagePtr image, QString filename)
113 {
114  if (!image)
115  return false;
116  vtkImageDataPtr raw = this->loadVtkImageData(filename);
117  if(!raw)
118  return false;
119  image->setVtkImageData(raw);
120  return true;
121 }
122 
123 DataPtr DICOMReader::read(const QString& uid, const QString& filename)
124 {
125  ImagePtr image(new Image(uid, vtkImageDataPtr()));
126  this->readInto(image, filename);
127 
128  DataPtr retval = image;
129  return retval;
130 }
131 
132 std::vector<DataPtr> DICOMReader::read(const QString &filename)
133 {
134  std::vector<DataPtr> retval;
135  std::vector<ImagePtr> images = importSeries(filename, false);
136 
137  for(int i = 0; i < images.size(); ++i)
138  retval.push_back(images[i]);
139 
140 // CX_LOG_DEBUG() << "Found " << retval.size() << " DICOM series";
141  return retval;
142 }
143 
145 {
146  bool readBestSeries = true;
147  std::vector<ImagePtr> images = importSeries(filename, readBestSeries);
148  return images[0]->getBaseVtkImageData();
149 }
150 
152 {
153  return "";
154 }
155 
156 bool DICOMReader::canWrite(const QString &type, const QString &filename) const
157 {
158  return false;
159 }
160 
161 //Copied from DicomWidget::importSeries
162 //Also copied DicomConverter and DicomImageReader files from the dicom plugin
163 std::vector<ImagePtr> DICOMReader::importSeries(QString fileName, bool readBestSeries)
164 {
165  //Turn off Qt messages temporarily
166  CX_LOG_INFO() << "stopQtMessages while reading DICOM files";
167  reporter()->stopQtMessages();
168 
169  ctkDICOMDatabasePtr database = ctkDICOMDatabasePtr(new ctkDICOMDatabase);
170  database->openDatabase(":memory:");
171 
172  QFileInfo dir = QFileInfo(fileName);
173  QString folder = dir.absolutePath();
174  if(dir.isDir())
175  folder = fileName;
176 
177  QProgressDialog progress("Reading DICOM series...", "Cancel", 0, 0);
178  progress.setWindowModality(Qt::WindowModal);
179  progress.setMinimumDuration(0);
180  progress.setValue(0);
181 
182  QStringList dicomFolders = this->findAllSubfoldersWithDicomFiles(folder, progress);
183 
184  progress.setLabelText("Reading DICOM series...");
185  progress.setMaximum(dicomFolders.size());
186 
187  for(int i = 0; i < dicomFolders.size(); ++i)
188  {
189  progress.setValue(i);
190  if (progress.wasCanceled())
191  break;
192  addFolderToDicomDatabase(database,dicomFolders[i]);
193  }
194 
195  std::vector<ImagePtr> retval;
196  if(readBestSeries)
197  retval = importBestSeries(database);
198  else
199  retval = importAllSeries(database, progress);
200 
201  database->closeDatabase();
202 
203  //Turn Qt messages back on
204  reporter()->startQtMessages();
205  CX_LOG_INFO() << "DICOM files read - startQtMessages";
206  return retval;
207 }
208 
209 
210 QStringList DICOMReader::findAllSubfoldersWithDicomFiles(QString folder, QProgressDialog &progress)
211 {
212  CX_LOG_INFO() << "Finding all subfolders in: " << folder;
213  QStringList subDirs = findAllSubDirs(folder);
214 
215  progress.setLabelText("Searcing all subdirectories for DICOM series: "+folder);
216  progress.setMaximum(subDirs.size());
217  QStringList retval;
218  for(int i = 0; i < subDirs.size(); ++i)
219  {
220  progress.setValue(i);
221  if (progress.wasCanceled())
222  break;
223  if(this->canReadDir(subDirs[i], false))
224  retval << subDirs[i];
225  }
226  return retval;
227 }
228 
229 QStringList DICOMReader::findAllSubDirs(QString folder)
230 {
231  if(!QFileInfo(folder).isDir())
232  return QStringList();
233 
234  QStringList allSubDirs;
235 
236  QDir dir(folder);
237  QStringList files = dir.entryList(QDir::NoDotAndDotDot | QDir::Dirs);
238  for(int i = 0; i < files.size(); ++i)
239  {
240  QString fullPath = folder+"/"+files[i];
241  QStringList subDirs = findAllSubDirs(fullPath);
242  allSubDirs << subDirs;
243  }
244  allSubDirs << folder;
245 
246  return allSubDirs;
247 }
248 
250 {
251  QSharedPointer<ctkDICOMIndexer> DICOMIndexer = QSharedPointer<ctkDICOMIndexer> (new ctkDICOMIndexer); //TODO: Reuse instead on creating new one?
252  std::cout.setstate(std::ios_base::failbit);//Hack to silence std::cout
253  DICOMIndexer->addDirectory(*database,folder,"");//This function prints out (with std::cout) a list of all files (ctkDICOMIndexer.cpp, line 93)
254  std::cout.clear();//Turn on std::cout again
255 }
256 
258 {
259  //DCMTK sometimes prints a lot of warnings about zeroes in the data:
260  //W: DcmItem: Element (0000,0000) found twice in one dataset/item, ignoring second entry
261  //This can delay the application several minutes
262 
263  //Still this soution is also a bit slow. Removing the warning print line in DCMTK is faster:
264  //in DcmItem::readSubElement() - line 1104 and 1105:
265  //DCMDATA_WARN("DcmItem: Element " << newTag << " found twice in one dataset/item, ignoring second entry");
266 
267  //Example from: https://support.dcmtk.org/redmine/projects/dcmtk/wiki/howto_logprogram
268  dcmtk::log4cplus::SharedAppenderPtr nullapp(new dcmtk::log4cplus::NullAppender());
269  /* make sure that only the null logger is used */
270  dcmtk::log4cplus::Logger log = dcmtk::log4cplus::Logger::getRoot();
271  log.removeAllAppenders();
272  log.addAppender(nullapp);
273 }
274 
275 std::vector<ImagePtr> DICOMReader::importBestSeries(ctkDICOMDatabasePtr database)
276 {
277  std::vector<ImagePtr> retval;
278  QString seriesUid = this->getBestDICOMSeries(database);
279 
280  cx::DicomConverter converter;
281  converter.setDicomDatabase(database.data());
282  cx::ImagePtr convertedImage = converter.convertToImage(seriesUid);
283 
284  if (convertedImage)
285  retval.push_back(convertedImage);
286  return retval;
287 }
288 
289 std::vector<ImagePtr> DICOMReader::importAllSeries(ctkDICOMDatabasePtr database, QProgressDialog &progress)
290 {
291  QStringList allSeriesUid = this->getAllDICOMSeries(database);
292 
293  progress.setLabelText("Converting DICOM series...");
294  progress.setMaximum(allSeriesUid.size());
295 
296  cx::DicomConverter converter;
297  converter.setDicomDatabase(database.data());
298 
299  std::vector<ImagePtr> retval;
300  for(int i = 0; i < allSeriesUid.size(); ++i)
301  {
302  progress.setValue(i);
303  if (progress.wasCanceled())
304  break;
305  cx::ImagePtr convertedImage = converter.convertToImage(allSeriesUid[i]);
306 
307  if (convertedImage)
308  retval.push_back(convertedImage);
309  else
310  reportError(QString("Failed to convert DICOM series %1").arg(allSeriesUid[i]));
311  }
312 
313  return retval;
314 }
315 
317 {
318  QStringList series;
319  QStringList patients = database->patients();
320  for(int pNr = 0; pNr < patients.size(); ++pNr)
321  {
322  QString patient = patients[pNr];
323 // CX_LOG_DEBUG() << "Got " << patients.size() << " DICOM patients.";
324  QStringList studies = database->studiesForPatient(patient);
325 // CX_LOG_DEBUG() << "Got " << studies.size() << " DICOM studies for patient " << patient;
326  for(int sNr = 0; sNr < studies.size(); ++sNr)
327  {
328  QString study = studies[sNr];
329  series << database->seriesForStudy(study);
330  }
331  }
332  return series;
333 }
334 
335 //Choose the series with the most files for now
337 {
338  QStringList series = getAllDICOMSeries(database);
339  QString retval;
340  int numFiles = 0;
341 
342  for(int seriesNr = 0; seriesNr < series.size(); ++seriesNr)
343  {
344  QString serie = series[seriesNr];
345  QStringList files = database->filesForSeries(serie);
346 // CX_LOG_DEBUG() << "Got " << files.size() << " DICOM files for series " << serie;
347  if(numFiles < files.size())
348  {
349  numFiles = files.size();
350  retval = serie;
351  }
352  }
353  return retval;
354 }
355 
356 }//cx
virtual vtkImageDataPtr loadVtkImageData(QString filename)
void reportError(QString msg)
Definition: cxLogger.cpp:71
QStringList findAllSubfoldersWithDicomFiles(QString folder, QProgressDialog &progress)
std::vector< ImagePtr > importSeries(QString fileName, bool readBestSeries)
bool canReadFile(QString filename)
ReporterPtr reporter()
Definition: cxReporter.cpp:36
QString canWriteDataType() const
DICOMReader(PatientModelServicePtr patientModelService)
virtual bool canRead(const QString &type, const QString &filename)
Simple check if file is a DICOM file. DICOM files can have several different endings (...
#define CX_LOG_INFO
Definition: cxLogger.h:96
boost::shared_ptr< class Image > ImagePtr
Definition: cxDicomWidget.h:27
void setDicomDatabase(ctkDICOMDatabase *database)
bool canWrite(const QString &type, const QString &filename) const
boost::shared_ptr< class Data > DataPtr
ImagePtr convertToImage(QString seriesUid)
std::vector< ImagePtr > importAllSeries(ctkDICOMDatabasePtr database, QProgressDialog &progress)
virtual QString canReadDataType() const
virtual DataPtr read(const QString &uid, const QString &filename)
virtual bool readInto(DataPtr data, QString path)
A volumetric data set.
Definition: cxImage.h:45
QStringList findAllSubDirs(QString folder)
boost::shared_ptr< class PatientModelService > PatientModelServicePtr
static QString getTypeName()
Definition: cxImage.h:126
void addFolderToDicomDatabase(ctkDICOMDatabasePtr database, QString folder)
QString getBestDICOMSeries(ctkDICOMDatabasePtr database)
QStringList getAllDICOMSeries(ctkDICOMDatabasePtr database)
QSharedPointer< class ctkDICOMDatabase > ctkDICOMDatabasePtr
Definition: cxDICOMReader.h:17
std::vector< ImagePtr > importBestSeries(ctkDICOMDatabasePtr database)
bool canReadDir(QString dirname, bool checkSubDirs)
vtkSmartPointer< class vtkImageData > vtkImageDataPtr
Namespace for all CustusX production code.