qCTKDCMTKIndexer.cxx 14 KB

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