qCTKDCMTKIndexer.cxx 15 KB

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