ctkDICOMIndexer.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. /*=========================================================================
  2. Library: CTK
  3. Copyright (c) Kitware Inc.
  4. All rights reserved.
  5. Distributed under a BSD License. See LICENSE.txt file.
  6. This software is distributed "AS IS" WITHOUT ANY WARRANTY; without even
  7. the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  8. See the above copyright notice for more information.
  9. =========================================================================*/
  10. // Qt includes
  11. #include <QSqlQuery>
  12. #include <QSqlRecord>
  13. #include <QVariant>
  14. #include <QDate>
  15. #include <QStringList>
  16. #include <QSet>
  17. #include <QFile>
  18. #include <QDirIterator>
  19. #include <QFileInfo>
  20. #include <QDebug>
  21. // ctkDICOM includes
  22. #include "ctkDICOMIndexer.h"
  23. // DCMTK includes
  24. #ifndef WIN32
  25. #define HAVE_CONFIG_H
  26. #endif
  27. #include <dcmtk/dcmdata/dcfilefo.h>
  28. #include <dcmtk/dcmdata/dcfilefo.h>
  29. #include <dcmtk/dcmdata/dcdeftag.h>
  30. #include <dcmtk/dcmdata/dcdatset.h>
  31. #include <dcmtk/ofstd/ofcond.h>
  32. #include <dcmtk/ofstd/ofstring.h>
  33. #include <dcmtk/ofstd/ofstd.h> /* for class OFStandard */
  34. #include <dcmtk/dcmdata/dcddirif.h> /* for class DicomDirInterface */
  35. #define MITK_ERROR std::cout
  36. #define MITK_INFO std::cout
  37. //------------------------------------------------------------------------------
  38. class ctkDICOMIndexerPrivate: public ctkPrivate<ctkDICOMIndexer>
  39. {
  40. public:
  41. ctkDICOMIndexerPrivate();
  42. ~ctkDICOMIndexerPrivate();
  43. };
  44. //------------------------------------------------------------------------------
  45. // ctkDICOMIndexerPrivate methods
  46. //------------------------------------------------------------------------------
  47. ctkDICOMIndexerPrivate::ctkDICOMIndexerPrivate()
  48. {
  49. }
  50. //------------------------------------------------------------------------------
  51. ctkDICOMIndexerPrivate::~ctkDICOMIndexerPrivate()
  52. {
  53. }
  54. //------------------------------------------------------------------------------
  55. // ctkDICOMIndexer methods
  56. //------------------------------------------------------------------------------
  57. ctkDICOMIndexer::ctkDICOMIndexer()
  58. {
  59. }
  60. //------------------------------------------------------------------------------
  61. ctkDICOMIndexer::~ctkDICOMIndexer()
  62. {
  63. }
  64. //------------------------------------------------------------------------------
  65. void ctkDICOMIndexer::addDirectory(QSqlDatabase database, const QString& directoryName,const QString& destinationDirectoryName)
  66. {
  67. QSqlDatabase db = database;
  68. const std::string src_directory(directoryName.toStdString());
  69. OFList<OFString> originalDcmtkFileNames;
  70. OFList<OFString> dcmtkFileNames;
  71. OFStandard::searchDirectoryRecursively(src_directory.c_str(), originalDcmtkFileNames, NULL, NULL);
  72. // hack to reverse list of filenames (not neccessary when image loading works correctly)
  73. for ( OFListIterator(OFString) iter = originalDcmtkFileNames.begin(); iter != originalDcmtkFileNames.end(); ++iter )
  74. {
  75. dcmtkFileNames.push_front( *iter );
  76. }
  77. DcmFileFormat fileformat;
  78. OFListIterator(OFString) iter = dcmtkFileNames.begin();
  79. OFListIterator(OFString) last = dcmtkFileNames.end();
  80. if(iter == last) return;
  81. QSqlQuery query(database);
  82. /// these are for optimizing the import of image sequences
  83. /// since most information are identical for all slices
  84. OFString lastPatientID = "";
  85. OFString lastPatientsName = "";
  86. OFString lastPatientsBirthDate = "";
  87. OFString lastStudyInstanceUID = "";
  88. OFString lastSeriesInstanceUID = "";
  89. int lastPatientUID = -1;
  90. /* iterate over all input filenames */
  91. while (iter != last)
  92. {
  93. std::string filename((*iter).c_str());
  94. QString qfilename(filename.c_str());
  95. /// first we check if the file is already in the database
  96. QSqlQuery fileExists(database);
  97. fileExists.prepare("SELECT InsertTimestamp FROM Images WHERE Filename == ?");
  98. fileExists.bindValue(0,qfilename);
  99. fileExists.exec();
  100. if (
  101. fileExists.next() &&
  102. QFileInfo(qfilename).lastModified() < QDateTime::fromString(fileExists.value(0).toString(),Qt::ISODate)
  103. )
  104. {
  105. MITK_INFO << "File " << filename << " already added.";
  106. continue;
  107. }
  108. MITK_INFO << filename << "\n";
  109. OFCondition status = fileformat.loadFile(filename.c_str());
  110. ++iter;
  111. if (!status.good())
  112. {
  113. MITK_ERROR << "Could not load " << filename << "\nDCMTK says: " << status.text();
  114. continue;
  115. }
  116. OFString patientsName, patientID, patientsBirthDate, patientsBirthTime, patientsSex,
  117. patientComments, patientsAge;
  118. OFString studyInstanceUID, studyID, studyDate, studyTime,
  119. accessionNumber, modalitiesInStudy, institutionName, performingPhysiciansName, referringPhysician, studyDescription;
  120. OFString seriesInstanceUID, seriesDate, seriesTime,
  121. seriesDescription, bodyPartExamined, frameOfReferenceUID,
  122. contrastAgent, scanningSequence;
  123. OFString instanceNumber;
  124. Sint32 seriesNumber = 0, acquisitionNumber = 0, echoNumber = 0, temporalPosition = 0;
  125. //The patient UID is a unique number within the database, generated by the sqlite autoincrement
  126. int patientUID = -1;
  127. //If the following fields can not be evaluated, cancel evaluation of the DICOM file
  128. if (!fileformat.getDataset()->findAndGetOFString(DCM_PatientsName, patientsName).good())
  129. {
  130. MITK_ERROR << "Could not read DCM_PatientsName from " << filename;
  131. continue;
  132. }
  133. if (!fileformat.getDataset()->findAndGetOFString(DCM_StudyInstanceUID, studyInstanceUID).good())
  134. {
  135. MITK_ERROR << "Could not read DCM_StudyInstanceUID from " << filename;
  136. continue;
  137. }
  138. if (!fileformat.getDataset()->findAndGetOFString(DCM_SeriesInstanceUID, seriesInstanceUID).good())
  139. {
  140. MITK_ERROR << "Could not read DCM_SeriesInstanceUID from " << filename;
  141. continue;
  142. }
  143. if (!fileformat.getDataset()->findAndGetOFString(DCM_InstanceNumber, instanceNumber).good())
  144. {
  145. MITK_ERROR << "Could not read DCM_InstanceNumber from " << filename;
  146. continue;
  147. }
  148. fileformat.getDataset()->findAndGetOFString(DCM_PatientID, patientID);
  149. fileformat.getDataset()->findAndGetOFString(DCM_PatientsBirthDate, patientsBirthDate);
  150. fileformat.getDataset()->findAndGetOFString(DCM_PatientsBirthTime, patientsBirthTime);
  151. fileformat.getDataset()->findAndGetOFString(DCM_PatientsSex, patientsSex);
  152. fileformat.getDataset()->findAndGetOFString(DCM_PatientsAge, patientsAge);
  153. fileformat.getDataset()->findAndGetOFString(DCM_PatientComments, patientComments);
  154. fileformat.getDataset()->findAndGetOFString(DCM_StudyID, studyID);
  155. fileformat.getDataset()->findAndGetOFString(DCM_StudyDate, studyDate);
  156. fileformat.getDataset()->findAndGetOFString(DCM_StudyTime, studyTime);
  157. fileformat.getDataset()->findAndGetOFString(DCM_AccessionNumber, accessionNumber);
  158. fileformat.getDataset()->findAndGetOFString(DCM_ModalitiesInStudy, modalitiesInStudy);
  159. fileformat.getDataset()->findAndGetOFString(DCM_InstitutionName, institutionName);
  160. fileformat.getDataset()->findAndGetOFString(DCM_PerformingPhysiciansName, performingPhysiciansName);
  161. fileformat.getDataset()->findAndGetOFString(DCM_ReferringPhysiciansName, referringPhysician);
  162. fileformat.getDataset()->findAndGetOFString(DCM_StudyDescription, studyDescription);
  163. fileformat.getDataset()->findAndGetOFString(DCM_SeriesDate, seriesDate);
  164. fileformat.getDataset()->findAndGetOFString(DCM_SeriesTime, seriesTime);
  165. fileformat.getDataset()->findAndGetOFString(DCM_SeriesDescription, seriesDescription);
  166. fileformat.getDataset()->findAndGetOFString(DCM_BodyPartExamined, bodyPartExamined);
  167. fileformat.getDataset()->findAndGetOFString(DCM_FrameOfReferenceUID, frameOfReferenceUID);
  168. fileformat.getDataset()->findAndGetOFString(DCM_ContrastBolusAgent, contrastAgent);
  169. fileformat.getDataset()->findAndGetOFString(DCM_ScanningSequence, scanningSequence);
  170. fileformat.getDataset()->findAndGetSint32(DCM_SeriesNumber, seriesNumber);
  171. fileformat.getDataset()->findAndGetSint32(DCM_AcquisitionNumber, acquisitionNumber);
  172. fileformat.getDataset()->findAndGetSint32(DCM_EchoNumbers, echoNumber);
  173. fileformat.getDataset()->findAndGetSint32(DCM_TemporalPositionIdentifier, temporalPosition);
  174. MITK_INFO << "Adding new items to database:";
  175. MITK_INFO << "studyID: " << studyID;
  176. MITK_INFO << "seriesInstanceUID: " << seriesInstanceUID;
  177. MITK_INFO << "Patient's Name: " << patientsName;
  178. //-----------------------
  179. //Add Patient to Database
  180. //-----------------------
  181. //Speed up: Check if patient is the same as in last file; very probable, as all images belonging to a study have the same patient
  182. bool patientExists = false;
  183. if(lastPatientID.compare(patientID) || lastPatientsBirthDate.compare(patientsBirthDate) || lastPatientsName.compare(patientsName))
  184. {
  185. //Check if patient is already present in the db
  186. QSqlQuery check_exists_query(database);
  187. std::stringstream check_exists_query_string;
  188. check_exists_query_string << "SELECT * FROM Patients WHERE PatientID = '" << patientID << "'";
  189. check_exists_query.exec(check_exists_query_string.str().c_str());
  190. /// we check only patients with the same PatientID
  191. /// PatientID is not unique in DICOM, so we also compare Name and BirthDate
  192. /// and assume this is sufficient
  193. while (check_exists_query.next())
  194. {
  195. if (
  196. check_exists_query.record().value("PatientsName").toString() == patientsName.c_str() &&
  197. check_exists_query.record().value("PatientsBirthDate").toString() == patientsBirthDate.c_str()
  198. )
  199. {
  200. /// found it
  201. patientUID = check_exists_query.value(check_exists_query.record().indexOf("UID")).toInt();
  202. break;
  203. }
  204. }
  205. if(!patientExists)
  206. {
  207. std::stringstream query_string;
  208. query_string << "INSERT INTO Patients VALUES( NULL,'"
  209. << patientsName << "','"
  210. << patientID << "','"
  211. << patientsBirthDate << "','"
  212. << patientsBirthTime << "','"
  213. << patientsSex << "','"
  214. << patientsAge << "','"
  215. << patientComments << "')";
  216. query.exec(query_string.str().c_str());
  217. patientUID = query.lastInsertId().toInt();
  218. MITK_INFO << "New patient inserted: " << patientUID << "\n";
  219. }
  220. }
  221. else
  222. {
  223. patientUID = lastPatientUID;
  224. }
  225. /// keep this for the next image
  226. lastPatientUID = patientUID;
  227. lastPatientID = patientID;
  228. lastPatientsBirthDate = patientsBirthDate;
  229. lastPatientsName = patientsName;
  230. //---------------------
  231. //Add Study to Database
  232. //---------------------
  233. if(lastStudyInstanceUID.compare(studyInstanceUID))
  234. {
  235. QSqlQuery check_exists_query(database);
  236. std::stringstream check_exists_query_string;
  237. check_exists_query_string << "SELECT * FROM Studies WHERE StudyInstanceUID = '" << studyInstanceUID << "'";
  238. check_exists_query.exec(check_exists_query_string.str().c_str());
  239. if(!check_exists_query.next())
  240. {
  241. std::stringstream query_string;
  242. query_string << "INSERT INTO Studies VALUES('"
  243. << studyInstanceUID << "','"
  244. << patientUID << "','"
  245. << studyID << "','"
  246. << QDate::fromString(studyDate.c_str(), "yyyyMMdd").toString("yyyy-MM-dd").toStdString() << "','"
  247. << studyTime << "','"
  248. << accessionNumber << "','"
  249. << modalitiesInStudy << "','"
  250. << institutionName << "','"
  251. << referringPhysician << "','"
  252. << performingPhysiciansName << "','"
  253. << studyDescription << "')";
  254. query.exec(query_string.str().c_str());
  255. }
  256. }
  257. lastStudyInstanceUID = studyInstanceUID;
  258. //----------------------
  259. //Add Series to Database
  260. //----------------------
  261. if(lastSeriesInstanceUID.compare(seriesInstanceUID))
  262. {
  263. QSqlQuery check_exists_query(database);
  264. std::stringstream check_exists_query_string;
  265. check_exists_query_string << "SELECT * FROM Series WHERE SeriesInstanceUID = '" << seriesInstanceUID << "'";
  266. check_exists_query.exec(check_exists_query_string.str().c_str());
  267. if(!check_exists_query.next())
  268. {
  269. std::stringstream query_string;
  270. query_string << "INSERT INTO Series VALUES('"
  271. << seriesInstanceUID << "','" << studyInstanceUID << "','" << (int) seriesNumber << "','"
  272. << QDate::fromString(seriesDate.c_str(), "yyyyMMdd").toString("yyyy-MM-dd").toStdString() << "','"
  273. << seriesTime << "','" << seriesDescription << "','" << bodyPartExamined << "','"
  274. << frameOfReferenceUID << "','" << (int) acquisitionNumber << "','" << contrastAgent << "','"
  275. << scanningSequence << "','" << (int) echoNumber << "','" << (int) temporalPosition << "')";
  276. query.exec(query_string.str().c_str());
  277. }
  278. }
  279. lastSeriesInstanceUID = seriesInstanceUID;
  280. //----------------------------------
  281. //Move file to destination directory
  282. //----------------------------------
  283. if (!destinationDirectoryName.isEmpty())
  284. {
  285. QFile currentFile( qfilename );
  286. QDir destinationDir(destinationDirectoryName);
  287. QString uniqueDirName = QString(studyInstanceUID.c_str()) + "/" + seriesInstanceUID.c_str();
  288. qDebug() << "MKPath: " << uniqueDirName;
  289. destinationDir.mkpath(uniqueDirName);
  290. QString destFileName = destinationDir.absolutePath().append("/").append(instanceNumber.c_str());
  291. qDebug() << "Copy: " << qfilename << " -> " << destFileName;
  292. currentFile.copy(destFileName);
  293. //for testing only: copy file instead of moving
  294. //currentFile.copyTo(destDirectoryPath.str());
  295. }
  296. // */
  297. //------------------------
  298. //Add Filename to Database
  299. //------------------------
  300. // std::stringstream relativeFilePath;
  301. // relativeFilePath << seriesInstanceUID.c_str() << "/" << currentFilePath.getFileName();
  302. QSqlQuery check_exists_query(database);
  303. std::stringstream check_exists_query_string;
  304. // check_exists_query_string << "SELECT * FROM Images WHERE Filename = '" << relativeFilePath.str() << "'";
  305. check_exists_query_string << "SELECT * FROM Images WHERE Filename = '" << filename << "'";
  306. check_exists_query.exec(check_exists_query_string.str().c_str());
  307. if(!check_exists_query.next())
  308. {
  309. std::stringstream query_string;
  310. //To save absolute path: destDirectoryPath.str()
  311. query_string << "INSERT INTO Images VALUES('"
  312. << /*relativeFilePath.str()*/ filename << "','" << seriesInstanceUID << "','" << QDateTime::currentDateTime().toString(Qt::ISODate).toStdString() << "')";
  313. query.exec(query_string.str().c_str());
  314. }
  315. }
  316. // db.commit();
  317. // db.close();
  318. }
  319. //------------------------------------------------------------------------------
  320. void ctkDICOMIndexer::refreshDatabase(QSqlDatabase database, const QString& directoryName)
  321. {
  322. /// get all filenames from the database
  323. QSqlQuery allFilesQuery(database);
  324. QStringList databaseFileNames;
  325. QStringList filesToRemove;
  326. allFilesQuery.exec("SELECT Filename from Images;");
  327. while (allFilesQuery.next())
  328. {
  329. QString fileName = allFilesQuery.value(0).toString();
  330. databaseFileNames.append(fileName);
  331. if (! QFile::exists(fileName) )
  332. {
  333. filesToRemove.append(fileName);
  334. }
  335. }
  336. QSet<QString> filesytemFiles;
  337. QDirIterator dirIt(directoryName);
  338. while (dirIt.hasNext())
  339. {
  340. filesytemFiles.insert(dirIt.next());
  341. }
  342. }