qCTKDCMTKIndexer.cxx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  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. #define MITK_ERROR std::cout
  24. #define MITK_INFO std::cout
  25. class qCTKDCMTKIndexerPrivate: public qCTKPrivate<qCTKDCMTKIndexer>
  26. {
  27. public:
  28. qCTKDCMTKIndexerPrivate();
  29. ~qCTKDCMTKIndexerPrivate();
  30. };
  31. qCTKDCMTKIndexerPrivate::qCTKDCMTKIndexerPrivate()
  32. {
  33. }
  34. qCTKDCMTKIndexerPrivate::~qCTKDCMTKIndexerPrivate()
  35. {
  36. }
  37. qCTKDCMTKIndexer::qCTKDCMTKIndexer()
  38. {
  39. }
  40. qCTKDCMTKIndexer::~qCTKDCMTKIndexer()
  41. {
  42. }
  43. void qCTKDCMTKIndexer::AddDirectory(QSqlDatabase database, const QString& directoryName)
  44. {
  45. QSqlDatabase db = database;
  46. const std::string src_directory(directoryName.toStdString());
  47. // db.transaction();
  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. Sint32 seriesNumber = 0, acquisitionNumber = 0, echoNumber = 0, temporalPosition = 0;
  103. //The patient UID is a unique number within the database, generated by the sqlite autoincrement
  104. int patientUID = -1;
  105. //If the following fields can not be evaluated, cancel evaluation of the DICOM file
  106. if (!fileformat.getDataset()->findAndGetOFString(DCM_PatientsName, patientsName).good())
  107. {
  108. MITK_ERROR << "Could not read DCM_PatientsName from " << filename;
  109. continue;
  110. }
  111. if (!fileformat.getDataset()->findAndGetOFString(DCM_StudyInstanceUID, studyInstanceUID).good())
  112. {
  113. MITK_ERROR << "Could not read DCM_StudyInstanceUID from " << filename;
  114. continue;
  115. }
  116. if (!fileformat.getDataset()->findAndGetOFString(DCM_SeriesInstanceUID, seriesInstanceUID).good())
  117. {
  118. MITK_ERROR << "Could not read DCM_SeriesInstanceUID from " << filename;
  119. continue;
  120. }
  121. fileformat.getDataset()->findAndGetOFString(DCM_PatientID, patientID);
  122. fileformat.getDataset()->findAndGetOFString(DCM_PatientsBirthDate, patientsBirthDate);
  123. fileformat.getDataset()->findAndGetOFString(DCM_PatientsBirthTime, patientsBirthTime);
  124. fileformat.getDataset()->findAndGetOFString(DCM_PatientsSex, patientsSex);
  125. fileformat.getDataset()->findAndGetOFString(DCM_PatientsAge, patientsAge);
  126. fileformat.getDataset()->findAndGetOFString(DCM_PatientComments, patientComments);
  127. fileformat.getDataset()->findAndGetOFString(DCM_StudyID, studyID);
  128. fileformat.getDataset()->findAndGetOFString(DCM_StudyDate, studyDate);
  129. fileformat.getDataset()->findAndGetOFString(DCM_StudyTime, studyTime);
  130. fileformat.getDataset()->findAndGetOFString(DCM_AccessionNumber, accessionNumber);
  131. fileformat.getDataset()->findAndGetOFString(DCM_ModalitiesInStudy, modalitiesInStudy);
  132. fileformat.getDataset()->findAndGetOFString(DCM_InstitutionName, institutionName);
  133. fileformat.getDataset()->findAndGetOFString(DCM_PerformingPhysiciansName, performingPhysiciansName);
  134. fileformat.getDataset()->findAndGetOFString(DCM_ReferringPhysiciansName, referringPhysician);
  135. fileformat.getDataset()->findAndGetOFString(DCM_StudyDescription, studyDescription);
  136. fileformat.getDataset()->findAndGetOFString(DCM_SeriesDate, seriesDate);
  137. fileformat.getDataset()->findAndGetOFString(DCM_SeriesTime, seriesTime);
  138. fileformat.getDataset()->findAndGetOFString(DCM_SeriesDescription, seriesDescription);
  139. fileformat.getDataset()->findAndGetOFString(DCM_BodyPartExamined, bodyPartExamined);
  140. fileformat.getDataset()->findAndGetOFString(DCM_FrameOfReferenceUID, frameOfReferenceUID);
  141. fileformat.getDataset()->findAndGetOFString(DCM_ContrastBolusAgent, contrastAgent);
  142. fileformat.getDataset()->findAndGetOFString(DCM_ScanningSequence, scanningSequence);
  143. fileformat.getDataset()->findAndGetSint32(DCM_SeriesNumber, seriesNumber);
  144. fileformat.getDataset()->findAndGetSint32(DCM_AcquisitionNumber, acquisitionNumber);
  145. fileformat.getDataset()->findAndGetSint32(DCM_EchoNumbers, echoNumber);
  146. fileformat.getDataset()->findAndGetSint32(DCM_TemporalPositionIdentifier, temporalPosition);
  147. MITK_INFO << "Adding new items to database:";
  148. MITK_INFO << "studyID: " << studyID;
  149. MITK_INFO << "seriesInstanceUID: " << seriesInstanceUID;
  150. MITK_INFO << "Patient's Name: " << patientsName;
  151. //-----------------------
  152. //Add Patient to Database
  153. //-----------------------
  154. //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
  155. bool patientExists = false;
  156. if(lastPatientID.compare(patientID) || lastPatientsBirthDate.compare(patientsBirthDate) || lastPatientsName.compare(patientsName))
  157. {
  158. //Check if patient is already present in the db
  159. QSqlQuery check_exists_query(database);
  160. std::stringstream check_exists_query_string;
  161. check_exists_query_string << "SELECT * FROM Patients WHERE PatientID = '" << patientID << "'";
  162. check_exists_query.exec(check_exists_query_string.str().c_str());
  163. /// we check only patients with the same PatientID
  164. /// PatientID is not unique in DICOM, so we also compare Name and BirthDate
  165. /// and assume this is sufficient
  166. while (check_exists_query.next())
  167. {
  168. if (
  169. check_exists_query.record().value("PatientsName").toString() == patientsName.c_str() &&
  170. check_exists_query.record().value("PatientsBirthDate").toString() == patientsBirthDate.c_str()
  171. )
  172. {
  173. /// found it
  174. patientUID = check_exists_query.value(check_exists_query.record().indexOf("UID")).toInt();
  175. break;
  176. }
  177. }
  178. if(!patientExists)
  179. {
  180. std::stringstream query_string;
  181. query_string << "INSERT INTO Patients VALUES( NULL,'"
  182. << patientsName << "','"
  183. << patientID << "','"
  184. << patientsBirthDate << "','"
  185. << patientsBirthTime << "','"
  186. << patientsSex << "','"
  187. << patientsAge << "','"
  188. << patientComments << "')";
  189. query.exec(query_string.str().c_str());
  190. patientUID = query.lastInsertId().toInt();
  191. MITK_INFO << "New patient inserted: " << patientUID << "\n";
  192. }
  193. }
  194. else
  195. {
  196. patientUID = lastPatientUID;
  197. }
  198. /// keep this for the next image
  199. lastPatientUID = patientUID;
  200. lastPatientID = patientID;
  201. lastPatientsBirthDate = patientsBirthDate;
  202. lastPatientsName = patientsName;
  203. //---------------------
  204. //Add Study to Database
  205. //---------------------
  206. if(lastStudyInstanceUID.compare(studyInstanceUID))
  207. {
  208. QSqlQuery check_exists_query(database);
  209. std::stringstream check_exists_query_string;
  210. check_exists_query_string << "SELECT * FROM Studies WHERE StudyInstanceUID = '" << studyInstanceUID << "'";
  211. check_exists_query.exec(check_exists_query_string.str().c_str());
  212. if(!check_exists_query.next())
  213. {
  214. std::stringstream query_string;
  215. query_string << "INSERT INTO Studies VALUES('"
  216. << studyInstanceUID << "','"
  217. << patientUID << "','"
  218. << studyID << "','"
  219. << QDate::fromString(studyDate.c_str(), "yyyyMMdd").toString("yyyy-MM-dd").toStdString() << "','"
  220. << studyTime << "','"
  221. << accessionNumber << "','"
  222. << modalitiesInStudy << "','"
  223. << institutionName << "','"
  224. << referringPhysician << "','"
  225. << performingPhysiciansName << "','"
  226. << studyDescription << "')";
  227. query.exec(query_string.str().c_str());
  228. }
  229. }
  230. lastStudyInstanceUID = studyInstanceUID;
  231. //----------------------
  232. //Add Series to Database
  233. //----------------------
  234. if(lastSeriesInstanceUID.compare(seriesInstanceUID))
  235. {
  236. QSqlQuery check_exists_query(database);
  237. std::stringstream check_exists_query_string;
  238. check_exists_query_string << "SELECT * FROM Series WHERE SeriesInstanceUID = '" << seriesInstanceUID << "'";
  239. check_exists_query.exec(check_exists_query_string.str().c_str());
  240. if(!check_exists_query.next())
  241. {
  242. std::stringstream query_string;
  243. query_string << "INSERT INTO Series VALUES('"
  244. << seriesInstanceUID << "','" << studyInstanceUID << "','" << (int) seriesNumber << "','"
  245. << QDate::fromString(seriesDate.c_str(), "yyyyMMdd").toString("yyyy-MM-dd").toStdString() << "','"
  246. << seriesTime << "','" << seriesDescription << "','" << bodyPartExamined << "','"
  247. << frameOfReferenceUID << "','" << (int) acquisitionNumber << "','" << contrastAgent << "','"
  248. << scanningSequence << "','" << (int) echoNumber << "','" << (int) temporalPosition << "')";
  249. query.exec(query_string.str().c_str());
  250. }
  251. }
  252. lastSeriesInstanceUID = seriesInstanceUID;
  253. //----------------------------------
  254. //Move file to destination directory
  255. //----------------------------------
  256. /*
  257. // This depends on Poco and should be converted to Qt code
  258. Poco::File currentFile(filename);
  259. Poco::Path currentFilePath(filename);
  260. MITK_INFO << "currentFilePath.getFileName(): " << currentFilePath.getFileName() << "\n";
  261. if(moveFiles)
  262. {
  263. std::stringstream destDirectoryPath;
  264. if((dest_directory[dest_directory.length()-1] != '/') && (dest_directory[dest_directory.length()-1] != '\\'))
  265. destDirectoryPath << dest_directory << Poco::Path::separator() << seriesInstanceUID.c_str();
  266. else
  267. destDirectoryPath << dest_directory << seriesInstanceUID.c_str();
  268. MITK_INFO << "last symbol: " << dest_directory[dest_directory.length()-1]
  269. << "\ndestDirectoryPath: " << destDirectoryPath.str() << "\n";
  270. Poco::File directory(destDirectoryPath.str());
  271. if (!directory.exists()) directory.createDirectory();
  272. destDirectoryPath << Poco::Path::separator() << currentFilePath.getFileName();
  273. currentFile.moveTo(destDirectoryPath.str());
  274. //for testing only: copy file instead of moving
  275. //currentFile.copyTo(destDirectoryPath.str());
  276. }
  277. */
  278. //------------------------
  279. //Add Filename to Database
  280. //------------------------
  281. // std::stringstream relativeFilePath;
  282. // relativeFilePath << seriesInstanceUID.c_str() << "/" << currentFilePath.getFileName();
  283. QSqlQuery check_exists_query(database);
  284. std::stringstream check_exists_query_string;
  285. // check_exists_query_string << "SELECT * FROM Images WHERE Filename = '" << relativeFilePath.str() << "'";
  286. check_exists_query_string << "SELECT * FROM Images WHERE Filename = '" << filename << "'";
  287. check_exists_query.exec(check_exists_query_string.str().c_str());
  288. if(!check_exists_query.next())
  289. {
  290. std::stringstream query_string;
  291. //To save absolute path: destDirectoryPath.str()
  292. query_string << "INSERT INTO Images VALUES('"
  293. << /*relativeFilePath.str()*/ filename << "','" << seriesInstanceUID << "','" << QDateTime::currentDateTime().toString(Qt::ISODate).toStdString() << "')";
  294. query.exec(query_string.str().c_str());
  295. }
  296. }
  297. // db.commit();
  298. // db.close();
  299. }
  300. void qCTKDCMTKIndexer::refreshDatabase(QSqlDatabase database, const QString& directoryName)
  301. {
  302. /// get all filenames from the database
  303. QSqlQuery allFilesQuery(database);
  304. QStringList databaseFileNames;
  305. QStringList filesToRemove;
  306. allFilesQuery.exec("SELECT Filename from Images;");
  307. while (allFilesQuery.next())
  308. {
  309. QString fileName = allFilesQuery.value(0).toString();
  310. databaseFileNames.append(fileName);
  311. if (! QFile::exists(fileName) )
  312. {
  313. filesToRemove.append(fileName);
  314. }
  315. }
  316. QSet<QString> filesytemFiles;
  317. QDirIterator dirIt(directoryName);
  318. while (dirIt.hasNext())
  319. {
  320. filesytemFiles.insert(dirIt.next());
  321. }
  322. }