ctkDICOMIndexer.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. /*=========================================================================
  2. Library: CTK
  3. Copyright (c) Kitware Inc.
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0.txt
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. =========================================================================*/
  14. // Qt includes
  15. #include <QSqlQuery>
  16. #include <QSqlRecord>
  17. #include <QSqlError>
  18. #include <QVariant>
  19. #include <QDate>
  20. #include <QStringList>
  21. #include <QSet>
  22. #include <QFile>
  23. #include <QDirIterator>
  24. #include <QFileInfo>
  25. #include <QDebug>
  26. // ctkDICOM includes
  27. #include "ctkLogger.h"
  28. #include "ctkDICOMIndexer.h"
  29. #include "ctkDICOMIndexer_p.h"
  30. #include "ctkDICOMDatabase.h"
  31. // DCMTK includes
  32. #include <dcmtk/dcmdata/dcfilefo.h>
  33. #include <dcmtk/dcmdata/dcfilefo.h>
  34. #include <dcmtk/dcmdata/dcdeftag.h>
  35. #include <dcmtk/dcmdata/dcdatset.h>
  36. #include <dcmtk/ofstd/ofcond.h>
  37. #include <dcmtk/ofstd/ofstring.h>
  38. #include <dcmtk/ofstd/ofstd.h> /* for class OFStandard */
  39. #include <dcmtk/dcmdata/dcddirif.h> /* for class DicomDirInterface */
  40. #include <dcmtk/dcmimgle/dcmimage.h> /* for class DicomImage */
  41. #include <dcmtk/dcmimage/diregist.h> /* include support for color images */
  42. //------------------------------------------------------------------------------
  43. static ctkLogger logger("org.commontk.dicom.DICOMIndexer" );
  44. //------------------------------------------------------------------------------
  45. //------------------------------------------------------------------------------
  46. // ctkDICOMIndexerPrivate methods
  47. //------------------------------------------------------------------------------
  48. ctkDICOMIndexerPrivate::ctkDICOMIndexerPrivate(ctkDICOMIndexer& o)
  49. : q_ptr(&o)
  50. , Canceled(false)
  51. , StartedIndexing(0)
  52. {
  53. }
  54. //------------------------------------------------------------------------------
  55. ctkDICOMIndexerPrivate::~ctkDICOMIndexerPrivate()
  56. {
  57. }
  58. //------------------------------------------------------------------------------
  59. //------------------------------------------------------------------------------
  60. // ctkDICOMIndexer methods
  61. //------------------------------------------------------------------------------
  62. ctkDICOMIndexer::ctkDICOMIndexer(QObject *parent):d_ptr(new ctkDICOMIndexerPrivate(*this))
  63. {
  64. Q_UNUSED(parent);
  65. }
  66. //------------------------------------------------------------------------------
  67. ctkDICOMIndexer::~ctkDICOMIndexer()
  68. {
  69. }
  70. //------------------------------------------------------------------------------
  71. void ctkDICOMIndexer::addFile(ctkDICOMDatabase& database,
  72. const QString filePath,
  73. const QString& destinationDirectoryName)
  74. {
  75. ctkDICOMIndexer::ScopedIndexing indexingBatch(*this, database);
  76. if (!destinationDirectoryName.isEmpty())
  77. {
  78. logger.warn("Ignoring destinationDirectoryName parameter, just taking it as indication we should copy!");
  79. }
  80. emit indexingFilePath(filePath);
  81. database.insert(filePath, !destinationDirectoryName.isEmpty(), true);
  82. }
  83. //------------------------------------------------------------------------------
  84. void ctkDICOMIndexer::addDirectory(ctkDICOMDatabase& database,
  85. const QString& directoryName,
  86. const QString& destinationDirectoryName,
  87. bool includeHidden/*=true*/)
  88. {
  89. ctkDICOMIndexer::ScopedIndexing indexingBatch(*this, database);
  90. QStringList listOfFiles;
  91. QDir directory(directoryName);
  92. if(directory.exists("DICOMDIR"))
  93. {
  94. addDicomdir(database,directoryName,destinationDirectoryName);
  95. }
  96. else
  97. {
  98. QDir::Filters filters = QDir::Files;
  99. if (includeHidden)
  100. {
  101. filters |= QDir::Hidden;
  102. }
  103. QDirIterator it(directoryName, filters, QDirIterator::Subdirectories);
  104. while(it.hasNext())
  105. {
  106. listOfFiles << it.next();
  107. }
  108. emit foundFilesToIndex(listOfFiles.count());
  109. addListOfFiles(database,listOfFiles,destinationDirectoryName);
  110. }
  111. }
  112. //------------------------------------------------------------------------------
  113. void ctkDICOMIndexer::addListOfFiles(ctkDICOMDatabase& database,
  114. const QStringList& listOfFiles,
  115. const QString& destinationDirectoryName)
  116. {
  117. Q_D(ctkDICOMIndexer);
  118. ctkDICOMIndexer::ScopedIndexing indexingBatch(*this, database);
  119. QTime timeProbe;
  120. timeProbe.start();
  121. d->Canceled = false;
  122. int CurrentFileIndex = 0;
  123. int lastReportedPercent = 0;
  124. foreach(QString filePath, listOfFiles)
  125. {
  126. int percent = ( 100 * CurrentFileIndex ) / listOfFiles.size();
  127. if (lastReportedPercent / 10 < percent / 10)
  128. {
  129. // Reporting progress has a huge overhead (pending events are processed,
  130. // database is updated), therefore only report progress at every 10% increase
  131. emit this->progress(percent);
  132. lastReportedPercent = percent;
  133. }
  134. this->addFile(database, filePath, destinationDirectoryName);
  135. CurrentFileIndex++;
  136. if( d->Canceled )
  137. {
  138. break;
  139. }
  140. }
  141. float elapsedTimeInSeconds = timeProbe.elapsed() / 1000.0;
  142. qDebug()
  143. << QString("DICOM indexer has successfully processed %1 files [%2s]")
  144. .arg(CurrentFileIndex)
  145. .arg(QString::number(elapsedTimeInSeconds,'f', 2));
  146. }
  147. //------------------------------------------------------------------------------
  148. bool ctkDICOMIndexer::addDicomdir(ctkDICOMDatabase& database,
  149. const QString& directoryName,
  150. const QString& destinationDirectoryName
  151. )
  152. {
  153. ctkDICOMIndexer::ScopedIndexing indexingBatch(*this, database);
  154. //Initialize dicomdir with directory path
  155. QString dcmFilePath = directoryName;
  156. dcmFilePath.append("/DICOMDIR");
  157. DcmDicomDir* dicomDir = new DcmDicomDir(dcmFilePath.toStdString().c_str());
  158. //Values to store records data at the moment only uid needed
  159. OFString patientsName, studyInstanceUID, seriesInstanceUID, sopInstanceUID, referencedFileName ;
  160. //Variables for progress operations
  161. QString instanceFilePath;
  162. QStringList listOfInstances;
  163. DcmDirectoryRecord* rootRecord = &(dicomDir->getRootRecord());
  164. DcmDirectoryRecord* patientRecord = NULL;
  165. DcmDirectoryRecord* studyRecord = NULL;
  166. DcmDirectoryRecord* seriesRecord = NULL;
  167. DcmDirectoryRecord* fileRecord = NULL;
  168. QTime timeProbe;
  169. timeProbe.start();
  170. /*Iterate over all records in dicomdir and setup path to the dataset of the filerecord
  171. then insert. the filerecord into the database.
  172. If any UID is missing the record and all of it's subelements won't be added to the database*/
  173. bool success = true;
  174. if(rootRecord != NULL)
  175. {
  176. while ((patientRecord = rootRecord->nextSub(patientRecord)) != NULL)
  177. {
  178. logger.debug( "Reading new Patient:" );
  179. if (patientRecord->findAndGetOFString(DCM_PatientName, patientsName).bad())
  180. {
  181. logger.warn( "DICOMDIR file at "+directoryName+" is invalid: patient name not found. All records belonging to this patient will be ignored.");
  182. success = false;
  183. continue;
  184. }
  185. logger.debug( "Patient's Name: " + QString(patientsName.c_str()) );
  186. while ((studyRecord = patientRecord->nextSub(studyRecord)) != NULL)
  187. {
  188. logger.debug( "Reading new Study:" );
  189. if (studyRecord->findAndGetOFString(DCM_StudyInstanceUID, studyInstanceUID).bad())
  190. {
  191. logger.warn( "DICOMDIR file at "+directoryName+" is invalid: study instance UID not found for patient "+ QString(patientsName.c_str())+". All records belonging to this study will be ignored.");
  192. success = false;
  193. continue;
  194. }
  195. logger.debug( "Study instance UID: " + QString(studyInstanceUID.c_str()) );
  196. while ((seriesRecord = studyRecord->nextSub(seriesRecord)) != NULL)
  197. {
  198. logger.debug( "Reading new Series:" );
  199. if (seriesRecord->findAndGetOFString(DCM_SeriesInstanceUID, seriesInstanceUID).bad())
  200. {
  201. logger.warn( "DICOMDIR file at "+directoryName+" is invalid: series instance UID not found for patient "+ QString(patientsName.c_str())+", study "+ QString(studyInstanceUID.c_str())+". All records belonging to this series will be ignored.");
  202. success = false;
  203. continue;
  204. }
  205. logger.debug( "Series instance UID: " + QString(seriesInstanceUID.c_str()) );
  206. while ((fileRecord = seriesRecord->nextSub(fileRecord)) != NULL)
  207. {
  208. if (fileRecord->findAndGetOFStringArray(DCM_ReferencedSOPInstanceUIDInFile, sopInstanceUID).bad()
  209. || fileRecord->findAndGetOFStringArray(DCM_ReferencedFileID,referencedFileName).bad())
  210. {
  211. logger.warn( "DICOMDIR file at "+directoryName+" is invalid: referenced SOP instance UID or file name is invalid for patient "
  212. + QString(patientsName.c_str())+", study "+ QString(studyInstanceUID.c_str())+", series "+ QString(seriesInstanceUID.c_str())+
  213. ". This file will be ignored.");
  214. success = false;
  215. continue;
  216. }
  217. //Get the filepath of the instance and insert it into a list
  218. instanceFilePath = directoryName;
  219. instanceFilePath.append("/");
  220. instanceFilePath.append(QString( referencedFileName.c_str() ));
  221. instanceFilePath.replace("\\","/");
  222. listOfInstances << instanceFilePath;
  223. }
  224. }
  225. }
  226. }
  227. float elapsedTimeInSeconds = timeProbe.elapsed() / 1000.0;
  228. qDebug()
  229. << QString("DICOM indexer has successfully processed DICOMDIR in %1 [%2s]")
  230. .arg(directoryName)
  231. .arg(QString::number(elapsedTimeInSeconds,'f', 2));
  232. emit foundFilesToIndex(listOfInstances.count());
  233. addListOfFiles(database,listOfInstances,destinationDirectoryName);
  234. }
  235. return success;
  236. }
  237. //------------------------------------------------------------------------------
  238. void ctkDICOMIndexer::refreshDatabase(ctkDICOMDatabase& database, const QString& directoryName)
  239. {
  240. Q_UNUSED(database);
  241. Q_UNUSED(directoryName);
  242. /*
  243. * Probably this should go to the database class as well
  244. * Or we have to extend the interface to make possible what we do here
  245. * without using SQL directly
  246. /// get all filenames from the database
  247. QSqlQuery allFilesQuery(database.database());
  248. QStringList databaseFileNames;
  249. QStringList filesToRemove;
  250. this->loggedExec(allFilesQuery, "SELECT Filename from Images;");
  251. while (allFilesQuery.next())
  252. {
  253. QString fileName = allFilesQuery.value(0).toString();
  254. databaseFileNames.append(fileName);
  255. if (! QFile::exists(fileName) )
  256. {
  257. filesToRemove.append(fileName);
  258. }
  259. }
  260. QSet<QString> filesytemFiles;
  261. QDirIterator dirIt(directoryName);
  262. while (dirIt.hasNext())
  263. {
  264. filesytemFiles.insert(dirIt.next());
  265. }
  266. // TODO: it looks like this function was never finished...
  267. //
  268. // I guess the next step is to remove all filesToRemove from the database
  269. // and also to add filesystemFiles into the database tables
  270. */
  271. }
  272. //------------------------------------------------------------------------------
  273. void ctkDICOMIndexer::waitForImportFinished()
  274. {
  275. // No-op - this had been used when the indexing was multi-threaded,
  276. // and has only been retained for API compatibility.
  277. }
  278. //----------------------------------------------------------------------------
  279. void ctkDICOMIndexer::cancel()
  280. {
  281. Q_D(ctkDICOMIndexer);
  282. d->Canceled = true;
  283. }
  284. //----------------------------------------------------------------------------
  285. void ctkDICOMIndexer::startIndexing(ctkDICOMDatabase& database)
  286. {
  287. Q_D(ctkDICOMIndexer);
  288. if (d->StartedIndexing == 0)
  289. {
  290. // Indexing has just been started
  291. database.prepareInsert();
  292. }
  293. d->StartedIndexing++;
  294. }
  295. //----------------------------------------------------------------------------
  296. void ctkDICOMIndexer::endIndexing()
  297. {
  298. Q_D(ctkDICOMIndexer);
  299. d->StartedIndexing--;
  300. if (d->StartedIndexing == 0)
  301. {
  302. // Indexing has just been completed
  303. emit this->indexingComplete();
  304. }
  305. if (d->StartedIndexing < 0)
  306. {
  307. qWarning() << QString("ctkDICOMIndexer::endIndexing called without matching startIndexing");
  308. d->StartedIndexing = 0;
  309. }
  310. }