ctkDICOMDatabase.cpp 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052
  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. #include <stdexcept>
  15. // Qt includes
  16. #include <QSqlQuery>
  17. #include <QSqlRecord>
  18. #include <QSqlError>
  19. #include <QVariant>
  20. #include <QDate>
  21. #include <QStringList>
  22. #include <QSet>
  23. #include <QFile>
  24. #include <QDirIterator>
  25. #include <QFileInfo>
  26. #include <QDebug>
  27. #include <QFileSystemWatcher>
  28. // ctkDICOM includes
  29. #include "ctkDICOMDatabase.h"
  30. #include "ctkDICOMAbstractThumbnailGenerator.h"
  31. #include "ctkLogger.h"
  32. // DCMTK includes
  33. #include <dcmtk/dcmdata/dcfilefo.h>
  34. #include <dcmtk/dcmdata/dcfilefo.h>
  35. #include <dcmtk/dcmdata/dcdeftag.h>
  36. #include <dcmtk/dcmdata/dcdatset.h>
  37. #include <dcmtk/ofstd/ofcond.h>
  38. #include <dcmtk/ofstd/ofstring.h>
  39. #include <dcmtk/ofstd/ofstd.h> /* for class OFStandard */
  40. #include <dcmtk/dcmdata/dcddirif.h> /* for class DicomDirInterface */
  41. #include <dcmimage.h>
  42. #include <dcmtk/dcmjpeg/djdecode.h> /* for dcmjpeg decoders */
  43. #include <dcmtk/dcmjpeg/djencode.h> /* for dcmjpeg encoders */
  44. #include <dcmtk/dcmdata/dcrledrg.h> /* for DcmRLEDecoderRegistration */
  45. #include <dcmtk/dcmdata/dcrleerg.h> /* for DcmRLEEncoderRegistration */
  46. //------------------------------------------------------------------------------
  47. static ctkLogger logger("org.commontk.dicom.DICOMDatabase" );
  48. //------------------------------------------------------------------------------
  49. //------------------------------------------------------------------------------
  50. class ctkDICOMDatabasePrivate
  51. {
  52. Q_DECLARE_PUBLIC(ctkDICOMDatabase);
  53. protected:
  54. ctkDICOMDatabase* const q_ptr;
  55. public:
  56. ctkDICOMDatabasePrivate(ctkDICOMDatabase&);
  57. ~ctkDICOMDatabasePrivate();
  58. void init(QString databaseFile);
  59. void registerCompressionLibraries();
  60. bool executeScript(const QString script);
  61. ///
  62. /// \brief runs a query and prints debug output of status
  63. ///
  64. bool loggedExec(QSqlQuery& query);
  65. bool loggedExec(QSqlQuery& query, const QString& queryString);
  66. /// Name of the database file (i.e. for SQLITE the sqlite file)
  67. QString DatabaseFileName;
  68. QString LastError;
  69. QSqlDatabase Database;
  70. QMap<QString, QString> LoadedHeader;
  71. ctkDICOMAbstractThumbnailGenerator* thumbnailGenerator;
  72. /// these are for optimizing the import of image sequences
  73. /// since most information are identical for all slices
  74. OFString lastPatientID;
  75. OFString lastPatientsName;
  76. OFString lastPatientsBirthDate;
  77. OFString lastStudyInstanceUID;
  78. OFString lastSeriesInstanceUID;
  79. int lastPatientUID;
  80. };
  81. //------------------------------------------------------------------------------
  82. // ctkDICOMDatabasePrivate methods
  83. //------------------------------------------------------------------------------
  84. ctkDICOMDatabasePrivate::ctkDICOMDatabasePrivate(ctkDICOMDatabase& o): q_ptr(&o)
  85. {
  86. this->thumbnailGenerator = NULL;
  87. }
  88. //------------------------------------------------------------------------------
  89. void ctkDICOMDatabasePrivate::init(QString databaseFilename)
  90. {
  91. Q_Q(ctkDICOMDatabase);
  92. q->openDatabase(databaseFilename);
  93. }
  94. //------------------------------------------------------------------------------
  95. void ctkDICOMDatabasePrivate::registerCompressionLibraries(){
  96. logger.debug("Register compression libraries");
  97. // Register the JPEG libraries in case we need them
  98. // (registration only happens once, so it's okay to call repeatedly)
  99. // register global JPEG decompression codecs
  100. DJDecoderRegistration::registerCodecs();
  101. // register global JPEG compression codecs
  102. DJEncoderRegistration::registerCodecs();
  103. // register RLE compression codec
  104. DcmRLEEncoderRegistration::registerCodecs();
  105. // register RLE decompression codec
  106. DcmRLEDecoderRegistration::registerCodecs();
  107. }
  108. //------------------------------------------------------------------------------
  109. ctkDICOMDatabasePrivate::~ctkDICOMDatabasePrivate()
  110. {
  111. }
  112. //------------------------------------------------------------------------------
  113. bool ctkDICOMDatabasePrivate::loggedExec(QSqlQuery& query)
  114. {
  115. return (loggedExec(query, QString("")));
  116. }
  117. //------------------------------------------------------------------------------
  118. bool ctkDICOMDatabasePrivate::loggedExec(QSqlQuery& query, const QString& queryString)
  119. {
  120. bool success;
  121. if (queryString.compare(""))
  122. {
  123. success = query.exec(queryString);
  124. }
  125. else
  126. {
  127. success = query.exec();
  128. }
  129. if (!success)
  130. {
  131. QSqlError sqlError = query.lastError();
  132. logger.debug( "SQL failed\n Bad SQL: " + query.lastQuery());
  133. logger.debug( "Error text: " + sqlError.text());
  134. }
  135. else
  136. {
  137. logger.debug( "SQL worked!\n SQL: " + query.lastQuery());
  138. }
  139. return (success);
  140. }
  141. //------------------------------------------------------------------------------
  142. void ctkDICOMDatabase::openDatabase(const QString databaseFile, const QString& connectionName )
  143. {
  144. Q_D(ctkDICOMDatabase);
  145. d->DatabaseFileName = databaseFile;
  146. d->Database = QSqlDatabase::addDatabase("QSQLITE", connectionName);
  147. d->Database.setDatabaseName(databaseFile);
  148. if ( ! (d->Database.open()) )
  149. {
  150. d->LastError = d->Database.lastError().text();
  151. return;
  152. }
  153. if ( d->Database.tables().empty() )
  154. {
  155. if (!initializeDatabase())
  156. {
  157. d->LastError = QString("Unable to initialize DICOM database!");
  158. return;
  159. }
  160. }
  161. if (!isInMemory())
  162. {
  163. QFileSystemWatcher* watcher = new QFileSystemWatcher(QStringList(databaseFile),this);
  164. connect(watcher, SIGNAL(fileChanged(QString)),this, SIGNAL (databaseChanged()) );
  165. }
  166. }
  167. //------------------------------------------------------------------------------
  168. // ctkDICOMDatabase methods
  169. //------------------------------------------------------------------------------
  170. ctkDICOMDatabase::ctkDICOMDatabase(QString databaseFile)
  171. : d_ptr(new ctkDICOMDatabasePrivate(*this))
  172. {
  173. Q_D(ctkDICOMDatabase);
  174. d->registerCompressionLibraries();
  175. d->init(databaseFile);
  176. }
  177. ctkDICOMDatabase::ctkDICOMDatabase(QObject* parent)
  178. : d_ptr(new ctkDICOMDatabasePrivate(*this))
  179. {
  180. Q_UNUSED(parent);
  181. Q_D(ctkDICOMDatabase);
  182. d->registerCompressionLibraries();
  183. }
  184. //------------------------------------------------------------------------------
  185. ctkDICOMDatabase::~ctkDICOMDatabase()
  186. {
  187. }
  188. //----------------------------------------------------------------------------
  189. //------------------------------------------------------------------------------
  190. const QString ctkDICOMDatabase::lastError() const {
  191. Q_D(const ctkDICOMDatabase);
  192. return d->LastError;
  193. }
  194. //------------------------------------------------------------------------------
  195. const QString ctkDICOMDatabase::databaseFilename() const {
  196. Q_D(const ctkDICOMDatabase);
  197. return d->DatabaseFileName;
  198. }
  199. //------------------------------------------------------------------------------
  200. const QString ctkDICOMDatabase::databaseDirectory() const {
  201. QString databaseFile = databaseFilename();
  202. if (!QFileInfo(databaseFile).isAbsolute())
  203. {
  204. databaseFile.prepend(QDir::currentPath() + "/");
  205. }
  206. return QFileInfo ( databaseFile ).absoluteDir().path();
  207. }
  208. //------------------------------------------------------------------------------
  209. const QSqlDatabase& ctkDICOMDatabase::database() const {
  210. Q_D(const ctkDICOMDatabase);
  211. return d->Database;
  212. }
  213. void ctkDICOMDatabase::setThumbnailGenerator(ctkDICOMAbstractThumbnailGenerator *generator){
  214. Q_D(ctkDICOMDatabase);
  215. d->thumbnailGenerator = generator;
  216. }
  217. ctkDICOMAbstractThumbnailGenerator* ctkDICOMDatabase::thumbnailGenerator(){
  218. Q_D(const ctkDICOMDatabase);
  219. return d->thumbnailGenerator;
  220. }
  221. //------------------------------------------------------------------------------
  222. bool ctkDICOMDatabasePrivate::executeScript(const QString script) {
  223. QFile scriptFile(script);
  224. scriptFile.open(QIODevice::ReadOnly);
  225. if ( !scriptFile.isOpen() )
  226. {
  227. qDebug() << "Script file " << script << " could not be opened!\n";
  228. return false;
  229. }
  230. QString sqlCommands( QTextStream(&scriptFile).readAll() );
  231. sqlCommands.replace( '\n', ' ' );
  232. sqlCommands.remove( '\r' );
  233. sqlCommands.replace("; ", ";\n");
  234. QStringList sqlCommandsLines = sqlCommands.split('\n');
  235. QSqlQuery query(Database);
  236. for (QStringList::iterator it = sqlCommandsLines.begin(); it != sqlCommandsLines.end()-1; ++it)
  237. {
  238. if (! (*it).startsWith("--") )
  239. {
  240. qDebug() << *it << "\n";
  241. query.exec(*it);
  242. if (query.lastError().type())
  243. {
  244. qDebug() << "There was an error during execution of the statement: " << (*it);
  245. qDebug() << "Error message: " << query.lastError().text();
  246. return false;
  247. }
  248. }
  249. }
  250. return true;
  251. }
  252. //------------------------------------------------------------------------------
  253. bool ctkDICOMDatabase::initializeDatabase(const char* sqlFileName)
  254. {
  255. Q_D(ctkDICOMDatabase);
  256. return d->executeScript(sqlFileName);
  257. }
  258. //------------------------------------------------------------------------------
  259. void ctkDICOMDatabase::closeDatabase()
  260. {
  261. Q_D(ctkDICOMDatabase);
  262. d->Database.close();
  263. }
  264. //------------------------------------------------------------------------------
  265. QStringList ctkDICOMDatabase::patients()
  266. {
  267. Q_D(ctkDICOMDatabase);
  268. QSqlQuery query(d->Database);
  269. query.prepare ( "SELECT UID FROM Patients" );
  270. query.exec();
  271. QStringList result;
  272. while (query.next())
  273. {
  274. result << query.value(0).toString();
  275. }
  276. return( result );
  277. }
  278. //------------------------------------------------------------------------------
  279. QStringList ctkDICOMDatabase::studiesForPatient(QString patientUID)
  280. {
  281. Q_D(ctkDICOMDatabase);
  282. QSqlQuery query(d->Database);
  283. query.prepare ( "SELECT StudyInstanceUID FROM Studies WHERE PatientsUID = ?" );
  284. query.bindValue ( 0, patientUID );
  285. query.exec();
  286. QStringList result;
  287. while (query.next())
  288. {
  289. result << query.value(0).toString();
  290. }
  291. return( result );
  292. }
  293. //------------------------------------------------------------------------------
  294. QStringList ctkDICOMDatabase::seriesForStudy(QString studyUID)
  295. {
  296. Q_D(ctkDICOMDatabase);
  297. QSqlQuery query(d->Database);
  298. query.prepare ( "SELECT SeriesInstanceUID FROM Series WHERE StudyInstanceUID=?");
  299. query.bindValue ( 0, studyUID );
  300. query.exec();
  301. QStringList result;
  302. while (query.next())
  303. {
  304. result << query.value(0).toString();
  305. }
  306. return( result );
  307. }
  308. //------------------------------------------------------------------------------
  309. QStringList ctkDICOMDatabase::filesForSeries(QString seriesUID)
  310. {
  311. Q_D(ctkDICOMDatabase);
  312. QSqlQuery query(d->Database);
  313. query.prepare ( "SELECT Filename FROM Images WHERE SeriesInstanceUID=?");
  314. query.bindValue ( 0, seriesUID );
  315. query.exec();
  316. QStringList result;
  317. while (query.next())
  318. {
  319. result << query.value(0).toString();
  320. }
  321. return( result );
  322. }
  323. //------------------------------------------------------------------------------
  324. void ctkDICOMDatabase::loadInstanceHeader (QString sopInstanceUID)
  325. {
  326. Q_D(ctkDICOMDatabase);
  327. QSqlQuery query(d->Database);
  328. query.prepare ( "SELECT Filename FROM Images WHERE SOPInstanceUID=?");
  329. query.bindValue ( 0, sopInstanceUID );
  330. query.exec();
  331. d->LoadedHeader.clear();
  332. if (query.next())
  333. {
  334. QString fileName = query.value(0).toString();
  335. this->loadFileHeader(fileName);
  336. }
  337. return;
  338. }
  339. //------------------------------------------------------------------------------
  340. void ctkDICOMDatabase::loadFileHeader (QString fileName)
  341. {
  342. Q_D(ctkDICOMDatabase);
  343. DcmFileFormat fileFormat;
  344. OFCondition status = fileFormat.loadFile(fileName.toLatin1().data());
  345. if (status.good())
  346. {
  347. DcmDataset *dataset = fileFormat.getDataset();
  348. DcmStack stack;
  349. while (dataset->nextObject(stack, true) == EC_Normal)
  350. {
  351. DcmObject *dO = stack.top();
  352. if (dO->isaString())
  353. {
  354. QString tag = QString("%1,%2").arg(
  355. dO->getGTag(),4,16,QLatin1Char('0')).arg(
  356. dO->getETag(),4,16,QLatin1Char('0'));
  357. std::ostringstream s;
  358. dO->print(s);
  359. d->LoadedHeader[tag] = QString(s.str().c_str());
  360. }
  361. }
  362. }
  363. return;
  364. }
  365. //------------------------------------------------------------------------------
  366. QStringList ctkDICOMDatabase::headerKeys ()
  367. {
  368. Q_D(ctkDICOMDatabase);
  369. return (d->LoadedHeader.keys());
  370. }
  371. //------------------------------------------------------------------------------
  372. QString ctkDICOMDatabase::headerValue (QString key)
  373. {
  374. Q_D(ctkDICOMDatabase);
  375. return (d->LoadedHeader[key]);
  376. }
  377. //------------------------------------------------------------------------------
  378. /*
  379. void ctkDICOMDatabase::insert ( DcmDataset *dataset ) {
  380. this->insert ( dataset, QString() );
  381. }
  382. */
  383. //------------------------------------------------------------------------------
  384. void ctkDICOMDatabase::insert ( DcmDataset *dataset, bool storeFile, bool generateThumbnail)
  385. {
  386. Q_D(ctkDICOMDatabase);
  387. if (!dataset)
  388. {
  389. return;
  390. }
  391. // Check to see if the file has already been loaded
  392. OFString sopInstanceUID ;
  393. dataset->findAndGetOFString(DCM_SOPInstanceUID, sopInstanceUID);
  394. QSqlQuery fileExists ( d->Database );
  395. fileExists.prepare("SELECT InsertTimestamp,Filename FROM Images WHERE SOPInstanceUID == ?");
  396. fileExists.bindValue(0,QString(sopInstanceUID.c_str()));
  397. fileExists.exec();
  398. if ( fileExists.next() && QFileInfo(fileExists.value(1).toString()).lastModified() < QDateTime::fromString(fileExists.value(0).toString(),Qt::ISODate) )
  399. {
  400. logger.debug ( "File " + fileExists.value(1).toString() + " already added" );
  401. return;
  402. }
  403. OFString patientsName, patientID, patientsBirthDate, patientsBirthTime, patientsSex,
  404. patientComments, patientsAge;
  405. OFString studyInstanceUID, studyID, studyDate, studyTime,
  406. accessionNumber, modalitiesInStudy, institutionName, performingPhysiciansName, referringPhysician, studyDescription;
  407. OFString seriesInstanceUID, seriesDate, seriesTime,
  408. seriesDescription, bodyPartExamined, frameOfReferenceUID,
  409. contrastAgent, scanningSequence;
  410. OFString instanceNumber;
  411. Sint32 seriesNumber = 0, acquisitionNumber = 0, echoNumber = 0, temporalPosition = 0;
  412. //If the following fields can not be evaluated, cancel evaluation of the DICOM file
  413. dataset->findAndGetOFString(DCM_PatientName, patientsName);
  414. dataset->findAndGetOFString(DCM_StudyInstanceUID, studyInstanceUID);
  415. dataset->findAndGetOFString(DCM_SeriesInstanceUID, seriesInstanceUID);
  416. dataset->findAndGetOFString(DCM_PatientID, patientID);
  417. dataset->findAndGetOFString(DCM_PatientBirthDate, patientsBirthDate);
  418. dataset->findAndGetOFString(DCM_PatientBirthTime, patientsBirthTime);
  419. dataset->findAndGetOFString(DCM_PatientSex, patientsSex);
  420. dataset->findAndGetOFString(DCM_PatientAge, patientsAge);
  421. dataset->findAndGetOFString(DCM_PatientComments, patientComments);
  422. dataset->findAndGetOFString(DCM_StudyID, studyID);
  423. dataset->findAndGetOFString(DCM_StudyDate, studyDate);
  424. dataset->findAndGetOFString(DCM_StudyTime, studyTime);
  425. dataset->findAndGetOFString(DCM_AccessionNumber, accessionNumber);
  426. dataset->findAndGetOFString(DCM_ModalitiesInStudy, modalitiesInStudy);
  427. dataset->findAndGetOFString(DCM_InstitutionName, institutionName);
  428. dataset->findAndGetOFString(DCM_PerformingPhysicianName, performingPhysiciansName);
  429. dataset->findAndGetOFString(DCM_ReferringPhysicianName, referringPhysician);
  430. dataset->findAndGetOFString(DCM_StudyDescription, studyDescription);
  431. dataset->findAndGetOFString(DCM_SeriesDate, seriesDate);
  432. dataset->findAndGetOFString(DCM_SeriesTime, seriesTime);
  433. dataset->findAndGetOFString(DCM_SeriesDescription, seriesDescription);
  434. dataset->findAndGetOFString(DCM_BodyPartExamined, bodyPartExamined);
  435. dataset->findAndGetOFString(DCM_FrameOfReferenceUID, frameOfReferenceUID);
  436. dataset->findAndGetOFString(DCM_ContrastBolusAgent, contrastAgent);
  437. dataset->findAndGetOFString(DCM_ScanningSequence, scanningSequence);
  438. dataset->findAndGetSint32(DCM_SeriesNumber, seriesNumber);
  439. dataset->findAndGetSint32(DCM_AcquisitionNumber, acquisitionNumber);
  440. dataset->findAndGetSint32(DCM_EchoNumbers, echoNumber);
  441. dataset->findAndGetSint32(DCM_TemporalPositionIdentifier, temporalPosition);
  442. // store the file if the database is not in memomry
  443. QString filename;
  444. if ( storeFile && !this->isInMemory() )
  445. {
  446. DcmFileFormat* fileformat = new DcmFileFormat ( dataset );
  447. QString destinationDirectoryName = databaseDirectory() + "/dicom/";
  448. QDir destinationDir(destinationDirectoryName);
  449. QString studySeriesDirectory = QString(studyInstanceUID.c_str()) + "/" + seriesInstanceUID.c_str();
  450. destinationDir.mkpath(studySeriesDirectory);
  451. filename = databaseDirectory() + "/dicom/" + pathForDataset(dataset);
  452. logger.debug ( "Saving file: " + filename );
  453. OFCondition status = fileformat->saveFile ( filename.toAscii() );
  454. if ( !status.good() )
  455. {
  456. logger.error ( "Error saving file: " + filename + "\nError is " + status.text() );
  457. delete fileformat;
  458. return;
  459. }
  460. delete fileformat;
  461. }
  462. QSqlQuery check_exists_query(d->Database);
  463. //The patient UID is a unique number within the database, generated by the sqlite autoincrement
  464. int patientUID = -1;
  465. if ( patientID != "" && patientsName != "" )
  466. {
  467. //Check if patient is already present in the db
  468. check_exists_query.prepare ( "SELECT * FROM Patients WHERE PatientID = ? AND PatientsName = ?" );
  469. check_exists_query.bindValue ( 0, QString ( patientID.c_str() ) );
  470. check_exists_query.bindValue ( 1, QString ( patientsName.c_str() ) );
  471. check_exists_query.exec();
  472. if (check_exists_query.next())
  473. {
  474. patientUID = check_exists_query.value(check_exists_query.record().indexOf("UID")).toInt();
  475. }
  476. else
  477. {
  478. // Insert it
  479. QSqlQuery statement ( d->Database );
  480. statement.prepare ( "INSERT INTO Patients ('UID', 'PatientsName', 'PatientID', 'PatientsBirthDate', 'PatientsBirthTime', 'PatientsSex', 'PatientsAge', 'PatientsComments' ) values ( NULL, ?, ?, ?, ?, ?, ?, ? )" );
  481. statement.bindValue ( 0, QString ( patientsName.c_str() ) );
  482. statement.bindValue ( 1, QString ( patientID.c_str() ) );
  483. statement.bindValue ( 2, QString ( patientsBirthDate.c_str() ) );
  484. statement.bindValue ( 3, QString ( patientsBirthTime.c_str() ) );
  485. statement.bindValue ( 4, QString ( patientsSex.c_str() ) );
  486. // TODO: shift patient's age to study, since this is not a patient level attribute in images
  487. // statement.bindValue ( 5, QString ( patientsAge.c_str() ) );
  488. statement.bindValue ( 6, QString ( patientComments.c_str() ) );
  489. statement.exec ();
  490. patientUID = statement.lastInsertId().toInt();
  491. logger.debug ( "New patient inserted: " + QString().setNum ( patientUID ) );
  492. }
  493. }
  494. if ( studyInstanceUID != "" )
  495. {
  496. check_exists_query.prepare ( "SELECT * FROM Studies WHERE StudyInstanceUID = ?" );
  497. check_exists_query.bindValue ( 0, QString ( studyInstanceUID.c_str() ) );
  498. check_exists_query.exec();
  499. if(!check_exists_query.next())
  500. {
  501. QSqlQuery statement ( d->Database );
  502. statement.prepare ( "INSERT INTO Studies ( 'StudyInstanceUID', 'PatientsUID', 'StudyID', 'StudyDate', 'StudyTime', 'AccessionNumber', 'ModalitiesInStudy', 'InstitutionName', 'ReferringPhysician', 'PerformingPhysiciansName', 'StudyDescription' ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )" );
  503. statement.bindValue ( 0, QString ( studyInstanceUID.c_str() ) );
  504. statement.bindValue ( 1, patientUID );
  505. statement.bindValue ( 2, QString ( studyID.c_str() ) );
  506. statement.bindValue ( 3, QDate::fromString ( studyDate.c_str(), "yyyyMMdd" ) );
  507. statement.bindValue ( 4, QString ( studyTime.c_str() ) );
  508. statement.bindValue ( 5, QString ( accessionNumber.c_str() ) );
  509. statement.bindValue ( 6, QString ( modalitiesInStudy.c_str() ) );
  510. statement.bindValue ( 7, QString ( institutionName.c_str() ) );
  511. statement.bindValue ( 8, QString ( referringPhysician.c_str() ) );
  512. statement.bindValue ( 9, QString ( performingPhysiciansName.c_str() ) );
  513. statement.bindValue ( 10, QString ( studyDescription.c_str() ) );
  514. if ( !statement.exec() )
  515. {
  516. logger.error ( "Error executing statament: " + statement.lastQuery() + " Error: " + statement.lastError().text() );
  517. }
  518. }
  519. }
  520. if ( seriesInstanceUID != "" )
  521. {
  522. check_exists_query.prepare ( "SELECT * FROM Series WHERE SeriesInstanceUID = ?" );
  523. check_exists_query.bindValue ( 0, QString ( seriesInstanceUID.c_str() ) );
  524. logger.warn ( "Statement: " + check_exists_query.lastQuery() );
  525. check_exists_query.exec();
  526. if(!check_exists_query.next())
  527. {
  528. QSqlQuery statement ( d->Database );
  529. statement.prepare ( "INSERT INTO Series ( 'SeriesInstanceUID', 'StudyInstanceUID', 'SeriesNumber', 'SeriesDate', 'SeriesTime', 'SeriesDescription', 'BodyPartExamined', 'FrameOfReferenceUID', 'AcquisitionNumber', 'ContrastAgent', 'ScanningSequence', 'EchoNumber', 'TemporalPosition' ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )" );
  530. statement.bindValue ( 0, QString ( seriesInstanceUID.c_str() ) );
  531. statement.bindValue ( 1, QString ( studyInstanceUID.c_str() ) );
  532. statement.bindValue ( 2, static_cast<int>(seriesNumber) );
  533. statement.bindValue ( 3, QString ( seriesDate.c_str() ) );
  534. statement.bindValue ( 4, QDate::fromString ( seriesTime.c_str(), "yyyyMMdd" ) );
  535. statement.bindValue ( 5, QString ( seriesDescription.c_str() ) );
  536. statement.bindValue ( 6, QString ( bodyPartExamined.c_str() ) );
  537. statement.bindValue ( 7, QString ( frameOfReferenceUID.c_str() ) );
  538. statement.bindValue ( 8, static_cast<int>(acquisitionNumber) );
  539. statement.bindValue ( 9, QString ( contrastAgent.c_str() ) );
  540. statement.bindValue ( 10, QString ( scanningSequence.c_str() ) );
  541. statement.bindValue ( 11, static_cast<int>(echoNumber) );
  542. statement.bindValue ( 12, static_cast<int>(temporalPosition) );
  543. if ( !statement.exec() )
  544. {
  545. logger.error ( "Error executing statament: " + statement.lastQuery() + " Error: " + statement.lastError().text() );
  546. }
  547. }
  548. }
  549. if ( !filename.isEmpty() )
  550. {
  551. check_exists_query.prepare ( "SELECT * FROM Images WHERE Filename = ?" );
  552. check_exists_query.bindValue ( 0, filename );
  553. check_exists_query.exec();
  554. if(!check_exists_query.next())
  555. {
  556. QSqlQuery statement ( d->Database );
  557. statement.prepare ( "INSERT INTO Images ( 'SOPInstanceUID', 'Filename', 'SeriesInstanceUID', 'InsertTimestamp' ) VALUES ( ?, ?, ?, ? )" );
  558. statement.bindValue ( 0, QString ( sopInstanceUID.c_str() ) );
  559. statement.bindValue ( 1, filename );
  560. statement.bindValue ( 2, QString ( seriesInstanceUID.c_str() ) );
  561. statement.bindValue ( 3, QDateTime::currentDateTime() );
  562. statement.exec();
  563. }
  564. }
  565. if(generateThumbnail){
  566. if(d->thumbnailGenerator){
  567. QString studySeriesDirectory = QString(studyInstanceUID.c_str()) + "/" + QString(seriesInstanceUID.c_str());
  568. //Create thumbnail here
  569. QString thumbnailPath = databaseDirectory() +
  570. "/thumbs/" + this->pathForDataset(dataset) + ".png";
  571. //QString(studyInstanceUID.c_str()) + "/" +
  572. //QString(seriesInstanceUID.c_str()) + "/" +
  573. //QString(sopInstanceUID.c_str()) + ".png";
  574. QFileInfo thumbnailInfo(thumbnailPath);
  575. if(!(thumbnailInfo.exists() && (thumbnailInfo.lastModified() > QFileInfo(filename).lastModified()))){
  576. QDir(databaseDirectory() + "/thumbs/").mkpath(studySeriesDirectory);
  577. DicomImage dcmImage(QDir::toNativeSeparators(filename).toAscii());
  578. d->thumbnailGenerator->generateThumbnail(&dcmImage, thumbnailPath);
  579. }
  580. }
  581. }
  582. if (isInMemory())
  583. {
  584. emit databaseChanged();
  585. }
  586. }
  587. //------------------------------------------------------------------------------
  588. void ctkDICOMDatabase::insert ( const QString& filePath, bool storeFile, bool generateThumbnail, bool createHierarchy, const QString& destinationDirectoryName)
  589. {
  590. Q_D(ctkDICOMDatabase);
  591. /// first we check if the file is already in the database
  592. if (fileExistsAndUpToDate(filePath))
  593. {
  594. logger.debug( "File " + filePath + " already added.");
  595. return;
  596. }
  597. logger.debug( "Processing " + filePath );
  598. std::string filename = filePath.toStdString();
  599. DcmFileFormat fileformat;
  600. DcmDataset *dataset;
  601. OFCondition status = fileformat.loadFile(filename.c_str());
  602. dataset = fileformat.getDataset();
  603. if (!status.good())
  604. {
  605. logger.error( "Could not load " + filePath );
  606. logger.error( "DCMTK says: " + QString(status.text()) );
  607. return;
  608. }
  609. // ok, we have loaded a dataset
  610. OFString patientsName, patientID, patientsBirthDate, patientsBirthTime, patientsSex,
  611. patientComments, patientsAge;
  612. OFString studyInstanceUID, studyID, studyDate, studyTime,
  613. accessionNumber, modalitiesInStudy, institutionName, performingPhysiciansName, referringPhysician, studyDescription;
  614. OFString seriesInstanceUID, seriesDate, seriesTime,
  615. seriesDescription, bodyPartExamined, frameOfReferenceUID,
  616. contrastAgent, scanningSequence;
  617. OFString instanceNumber, sopInstanceUID ;
  618. Sint32 seriesNumber = 0, acquisitionNumber = 0, echoNumber = 0, temporalPosition = 0;
  619. //The patient UID is a unique number within the database, generated by the sqlite autoincrement
  620. //Thus, this is _not_ the DICOM Patient ID.
  621. int patientUID = -1;
  622. //If the following fields can not be evaluated, cancel evaluation of the DICOM file
  623. if (!dataset->findAndGetOFString(DCM_PatientName, patientsName).good())
  624. {
  625. logger.error( "Could not read DCM_PatientName from " + filePath );
  626. return;
  627. }
  628. if (!dataset->findAndGetOFString(DCM_StudyInstanceUID, studyInstanceUID).good())
  629. {
  630. logger.error( "Could not read DCM_StudyInstanceUID from " + filePath );
  631. return;
  632. }
  633. if (!dataset->findAndGetOFString(DCM_SeriesInstanceUID, seriesInstanceUID).good())
  634. {
  635. logger.error( "Could not read DCM_SeriesInstanceUID from " + filePath );
  636. return;
  637. }
  638. if (!dataset->findAndGetOFString(DCM_SOPInstanceUID, sopInstanceUID).good())
  639. {
  640. logger.error( "Could not read DCM_SOPInstanceUID from " + filePath );
  641. return;
  642. }
  643. if (!dataset->findAndGetOFString(DCM_InstanceNumber, instanceNumber).good())
  644. {
  645. logger.error( "Could not read DCM_InstanceNumber from " + filePath );
  646. return;
  647. }
  648. dataset->findAndGetOFString(DCM_PatientID, patientID);
  649. dataset->findAndGetOFString(DCM_PatientBirthDate, patientsBirthDate);
  650. dataset->findAndGetOFString(DCM_PatientBirthTime, patientsBirthTime);
  651. dataset->findAndGetOFString(DCM_PatientSex, patientsSex);
  652. dataset->findAndGetOFString(DCM_PatientAge, patientsAge);
  653. dataset->findAndGetOFString(DCM_PatientComments, patientComments);
  654. dataset->findAndGetOFString(DCM_StudyID, studyID);
  655. dataset->findAndGetOFString(DCM_StudyDate, studyDate);
  656. dataset->findAndGetOFString(DCM_StudyTime, studyTime);
  657. dataset->findAndGetOFString(DCM_AccessionNumber, accessionNumber);
  658. dataset->findAndGetOFString(DCM_ModalitiesInStudy, modalitiesInStudy);
  659. dataset->findAndGetOFString(DCM_InstitutionName, institutionName);
  660. dataset->findAndGetOFString(DCM_PerformingPhysicianName, performingPhysiciansName);
  661. dataset->findAndGetOFString(DCM_ReferringPhysicianName, referringPhysician);
  662. dataset->findAndGetOFString(DCM_StudyDescription, studyDescription);
  663. dataset->findAndGetOFString(DCM_SeriesDate, seriesDate);
  664. dataset->findAndGetOFString(DCM_SeriesTime, seriesTime);
  665. dataset->findAndGetOFString(DCM_SeriesDescription, seriesDescription);
  666. dataset->findAndGetOFString(DCM_BodyPartExamined, bodyPartExamined);
  667. dataset->findAndGetOFString(DCM_FrameOfReferenceUID, frameOfReferenceUID);
  668. dataset->findAndGetOFString(DCM_ContrastBolusAgent, contrastAgent);
  669. dataset->findAndGetOFString(DCM_ScanningSequence, scanningSequence);
  670. dataset->findAndGetSint32(DCM_SeriesNumber, seriesNumber);
  671. dataset->findAndGetSint32(DCM_AcquisitionNumber, acquisitionNumber);
  672. dataset->findAndGetSint32(DCM_EchoNumbers, echoNumber);
  673. dataset->findAndGetSint32(DCM_TemporalPositionIdentifier, temporalPosition);
  674. logger.debug( "Adding new items to database:" );
  675. logger.debug( "studyID: " + QString(studyID.c_str()) );
  676. logger.debug( "seriesInstanceUID: " + QString(seriesInstanceUID.c_str()) );
  677. logger.debug( "Patient's Name: " + QString(patientsName.c_str()) );
  678. //-----------------------
  679. //Add Patient to Database
  680. //-----------------------
  681. //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
  682. bool patientExists = false;
  683. if(d->lastPatientID.compare(patientID) || d->lastPatientsBirthDate.compare(patientsBirthDate) || d->lastPatientsName.compare(patientsName))
  684. {
  685. //Check if patient is already present in the db
  686. QSqlQuery check_exists_query(database());
  687. std::stringstream check_exists_query_string;
  688. check_exists_query_string << "SELECT * FROM Patients WHERE PatientID = '" << patientID << "'";
  689. d->loggedExec(check_exists_query, check_exists_query_string.str().c_str());
  690. /// we check only patients with the same PatientID
  691. /// PatientID is not unique in DICOM, so we also compare Name and BirthDate
  692. /// and assume this is sufficient
  693. while (check_exists_query.next())
  694. {
  695. if (
  696. check_exists_query.record().value("PatientsName").toString() == patientsName.c_str() &&
  697. check_exists_query.record().value("PatientsBirthDate").toString() == patientsBirthDate.c_str()
  698. )
  699. {
  700. /// found it
  701. patientUID = check_exists_query.value(check_exists_query.record().indexOf("UID")).toInt();
  702. patientExists = true;
  703. break;
  704. }
  705. }
  706. check_exists_query.finish();
  707. if(!patientExists)
  708. {
  709. QSqlQuery insert_patient_query(database());
  710. std::stringstream query_string;
  711. query_string << "INSERT INTO Patients VALUES( NULL,'"
  712. << patientsName << "','"
  713. << patientID << "','"
  714. << patientsBirthDate << "','"
  715. << patientsBirthTime << "','"
  716. << patientsSex << "','"
  717. << patientsAge << "','"
  718. << patientComments << "')";
  719. d->loggedExec(insert_patient_query, query_string.str().c_str());
  720. patientUID = insert_patient_query.lastInsertId().toInt();
  721. insert_patient_query.finish();
  722. QString patientUIDQString;
  723. patientUIDQString.setNum(patientUID);
  724. logger.debug( "New patient inserted: " + patientUIDQString );
  725. }
  726. }
  727. else
  728. {
  729. patientUID = d->lastPatientUID;
  730. }
  731. /// keep this for the next image
  732. d->lastPatientUID = patientUID;
  733. d->lastPatientID = patientID;
  734. d->lastPatientsBirthDate = patientsBirthDate;
  735. d->lastPatientsName = patientsName;
  736. //---------------------
  737. //Add Study to Database
  738. //---------------------
  739. if(d->lastStudyInstanceUID.compare(studyInstanceUID))
  740. {
  741. QSqlQuery check_exists_query(database());
  742. std::stringstream check_exists_query_string;
  743. check_exists_query_string << "SELECT * FROM Studies WHERE StudyInstanceUID = '" << studyInstanceUID << "'";
  744. d->loggedExec(check_exists_query, check_exists_query_string.str().c_str());
  745. logger.debug( "Checking for study: " + QString(studyInstanceUID.c_str()) );
  746. if(!check_exists_query.next())
  747. {
  748. QSqlQuery insert_query(database());
  749. std::stringstream query_string;
  750. // TODO: all INSERTS should be changed to use the prepare/bindValue methods
  751. // to avoid quoting issues
  752. insert_query.prepare("INSERT INTO Studies (StudyInstanceUID, PatientsUID, StudyID, StudyDate, StudyTime, AccessionNumber, ModalitiesInStudy, InstitutionName, ReferringPhysician, PerformingPhysiciansName, StudyDescription) VALUES (:StudyInstanceUID, :PatientsUID, :StudyID, :StudyDate, :StudyTime, :AccessionNumber, :ModalitiesInStudy, :InstitutionName, :ReferringPhysician, :PerformingPhysiciansName, :StudyDescription)");
  753. insert_query.bindValue(":StudyInstanceUID", QString(studyInstanceUID.c_str()));
  754. insert_query.bindValue(":PatientsUID", patientUID);
  755. insert_query.bindValue(":StudyID", QString(studyID.c_str()));
  756. insert_query.bindValue(":StudyDate", QDate::fromString(studyDate.c_str(), "yyyyMMdd").toString("yyyy-MM-dd"));
  757. insert_query.bindValue(":StudyTime", QString(studyTime.c_str()));
  758. insert_query.bindValue(":AccessionNumber", QString(accessionNumber.c_str()));
  759. insert_query.bindValue(":ModalitiesInStudy", QString(modalitiesInStudy.c_str()));
  760. insert_query.bindValue(":InstitutionName", QString(institutionName.c_str()));
  761. insert_query.bindValue(":ReferringPhysician", QString(referringPhysician.c_str()));
  762. insert_query.bindValue(":PerformingPhysiciansName", QString(performingPhysiciansName.c_str()));
  763. insert_query.bindValue(":StudyDescription", QString(studyDescription.c_str()));
  764. d->loggedExec(insert_query);
  765. logger.debug( "Inserted study: " + QString(studyInstanceUID.c_str()) );
  766. }
  767. }
  768. d->lastStudyInstanceUID = studyInstanceUID;
  769. //----------------------
  770. //Add Series to Database
  771. //----------------------
  772. if(d->lastSeriesInstanceUID.compare(seriesInstanceUID))
  773. {
  774. QSqlQuery check_exists_query(database());
  775. std::stringstream check_exists_query_string;
  776. check_exists_query_string << "SELECT * FROM Series WHERE SeriesInstanceUID = '" << seriesInstanceUID << "'";
  777. d->loggedExec(check_exists_query, check_exists_query_string.str().c_str());
  778. logger.debug( "Checking series: " + QString(seriesInstanceUID.c_str()) );
  779. if(!check_exists_query.next())
  780. {
  781. QSqlQuery insert_query(database());
  782. std::stringstream query_string;
  783. query_string << "INSERT INTO Series VALUES('"
  784. << seriesInstanceUID << "','"
  785. << studyInstanceUID << "','"
  786. << static_cast<int>(seriesNumber) << "','"
  787. << QDate::fromString(seriesDate.c_str(), "yyyyMMdd").toString("yyyy-MM-dd").toStdString() << "','"
  788. << seriesTime << "','"
  789. << seriesDescription << "','"
  790. << bodyPartExamined << "','"
  791. << frameOfReferenceUID << "','"
  792. << static_cast<int>(acquisitionNumber) << "','"
  793. << contrastAgent << "','"
  794. << scanningSequence << "','"
  795. << static_cast<int>(echoNumber) << "','"
  796. << static_cast<int>(temporalPosition) << "')";
  797. d->loggedExec(insert_query, query_string.str().c_str());
  798. logger.debug( "Inserted series: " + QString(seriesInstanceUID.c_str()) );
  799. }
  800. }
  801. d->lastSeriesInstanceUID = seriesInstanceUID;
  802. QString studySeriesDirectory = QString(studyInstanceUID.c_str()) + "/" + seriesInstanceUID.c_str();
  803. //----------------------------------
  804. //Move file to destination directory
  805. //----------------------------------
  806. QString finalFilePath(filePath);
  807. if (!destinationDirectoryName.isEmpty())
  808. {
  809. QFile currentFile( filePath );
  810. QDir destinationDir(destinationDirectoryName + "/dicom");
  811. finalFilePath = sopInstanceUID.c_str();
  812. if (createHierarchy)
  813. {
  814. destinationDir.mkpath(studySeriesDirectory);
  815. finalFilePath.prepend( destinationDir.absolutePath() + "/" + studySeriesDirectory + "/" );
  816. }
  817. currentFile.copy(finalFilePath);
  818. logger.debug( "Copy file from: " + filePath );
  819. logger.debug( "Copy file to : " + finalFilePath );
  820. }
  821. logger.debug(QString("finalFilePath: ") + finalFilePath);
  822. if (generateThumbnail)
  823. {
  824. if(d->thumbnailGenerator)
  825. {
  826. QString thumbnailBaseDir = databaseDirectory() + "/thumbs/";
  827. QString thumbnailFilename = thumbnailBaseDir + "/" + pathForDataset(dataset) + ".png";
  828. QFileInfo thumbnailInfo(thumbnailFilename);
  829. if ( ! ( thumbnailInfo.exists() && thumbnailInfo.lastModified() < QFileInfo(finalFilePath).lastModified() ) )
  830. {
  831. QDir(thumbnailBaseDir).mkpath(studySeriesDirectory);
  832. DicomImage dcmtkImage(QDir::toNativeSeparators(finalFilePath).toStdString().c_str());
  833. d->thumbnailGenerator->generateThumbnail(&dcmtkImage, thumbnailFilename);
  834. }
  835. }
  836. }
  837. //------------------------
  838. //Add Filename to Database
  839. //------------------------
  840. // std::stringstream relativeFilePath;
  841. // relativeFilePath << seriesInstanceUID.c_str() << "/" << currentFilePath.getFileName();
  842. logger.debug(QString("Adding file path to dabase: ") + finalFilePath);
  843. QSqlQuery check_exists_query(database());
  844. std::stringstream check_exists_query_string;
  845. // check_exists_query_string << "SELECT * FROM Images WHERE Filename = '" << relativeFilePath.str() << "'";
  846. check_exists_query_string << "SELECT * FROM Images WHERE SOPInstanceUID = '" << sopInstanceUID << "'";
  847. d->loggedExec(check_exists_query, check_exists_query_string.str().c_str());
  848. if(!check_exists_query.next())
  849. {
  850. QSqlQuery insert_query(database());
  851. std::stringstream query_string;
  852. //To save absolute path: destDirectoryPath.str()
  853. query_string << "INSERT INTO Images VALUES('"
  854. << sopInstanceUID << "','" << finalFilePath.toStdString() << "','" << seriesInstanceUID << "','" << QDateTime::currentDateTime().toString(Qt::ISODate).toStdString() << "')";
  855. d->loggedExec(insert_query, query_string.str().c_str());
  856. logger.debug(QString("added file path to dabase: ") + query_string.str().c_str());
  857. }
  858. }
  859. bool ctkDICOMDatabase::fileExistsAndUpToDate(const QString& filePath)
  860. {
  861. Q_D(ctkDICOMDatabase);
  862. bool result(false);
  863. QSqlQuery check_filename_query(database());
  864. check_filename_query.prepare("SELECT InsertTimestamp FROM Images WHERE Filename == ?");
  865. check_filename_query.bindValue(0,filePath);
  866. d->loggedExec(check_filename_query);
  867. if (
  868. check_filename_query.next() &&
  869. QFileInfo(filePath).lastModified() < QDateTime::fromString(check_filename_query.value(0).toString(),Qt::ISODate)
  870. )
  871. {
  872. result = true;
  873. }
  874. check_filename_query.finish();
  875. return result;
  876. }
  877. bool ctkDICOMDatabase::isOpen() const
  878. {
  879. Q_D(const ctkDICOMDatabase);
  880. return d->Database.isOpen();
  881. }
  882. bool ctkDICOMDatabase::isInMemory() const
  883. {
  884. Q_D(const ctkDICOMDatabase);
  885. return d->DatabaseFileName == ":memory:";
  886. }
  887. QString ctkDICOMDatabase::pathForDataset( DcmDataset *dataset)
  888. {
  889. if (!dataset)
  890. {
  891. return QString();
  892. }
  893. OFString studyInstanceUID, seriesInstanceUID, sopInstanceUID;
  894. dataset->findAndGetOFString(DCM_StudyInstanceUID, studyInstanceUID);
  895. dataset->findAndGetOFString(DCM_SeriesInstanceUID, seriesInstanceUID);
  896. dataset->findAndGetOFString(DCM_SOPInstanceUID, sopInstanceUID);
  897. return QString(studyInstanceUID.c_str()) + "/" + seriesInstanceUID.c_str() + "/" + sopInstanceUID.c_str();
  898. }