ctkDICOMDatabase.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809
  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 "ctkDICOMDataset.h"
  32. #include "ctkLogger.h"
  33. // DCMTK includes
  34. #include <dcmtk/dcmdata/dcfilefo.h>
  35. #include <dcmtk/dcmdata/dcfilefo.h>
  36. #include <dcmtk/dcmdata/dcdeftag.h>
  37. #include <dcmtk/dcmdata/dcdatset.h>
  38. #include <dcmtk/ofstd/ofcond.h>
  39. #include <dcmtk/ofstd/ofstring.h>
  40. #include <dcmtk/ofstd/ofstd.h> /* for class OFStandard */
  41. #include <dcmtk/dcmdata/dcddirif.h> /* for class DicomDirInterface */
  42. #include <dcmimage.h>
  43. #include <dcmtk/dcmjpeg/djdecode.h> /* for dcmjpeg decoders */
  44. #include <dcmtk/dcmjpeg/djencode.h> /* for dcmjpeg encoders */
  45. #include <dcmtk/dcmdata/dcrledrg.h> /* for DcmRLEDecoderRegistration */
  46. #include <dcmtk/dcmdata/dcrleerg.h> /* for DcmRLEEncoderRegistration */
  47. //------------------------------------------------------------------------------
  48. static ctkLogger logger("org.commontk.dicom.DICOMDatabase" );
  49. //------------------------------------------------------------------------------
  50. //------------------------------------------------------------------------------
  51. class ctkDICOMDatabasePrivate
  52. {
  53. Q_DECLARE_PUBLIC(ctkDICOMDatabase);
  54. protected:
  55. ctkDICOMDatabase* const q_ptr;
  56. public:
  57. ctkDICOMDatabasePrivate(ctkDICOMDatabase&);
  58. ~ctkDICOMDatabasePrivate();
  59. void init(QString databaseFile);
  60. void registerCompressionLibraries();
  61. bool executeScript(const QString script);
  62. ///
  63. /// \brief runs a query and prints debug output of status
  64. ///
  65. bool loggedExec(QSqlQuery& query);
  66. bool loggedExec(QSqlQuery& query, const QString& queryString);
  67. /// Name of the database file (i.e. for SQLITE the sqlite file)
  68. QString DatabaseFileName;
  69. QString LastError;
  70. QSqlDatabase Database;
  71. QMap<QString, QString> LoadedHeader;
  72. ctkDICOMAbstractThumbnailGenerator* thumbnailGenerator;
  73. /// these are for optimizing the import of image sequences
  74. /// since most information are identical for all slices
  75. QString lastPatientID;
  76. QString lastPatientsName;
  77. QString lastPatientsBirthDate;
  78. QString lastStudyInstanceUID;
  79. QString lastSeriesInstanceUID;
  80. int lastPatientUID;
  81. };
  82. //------------------------------------------------------------------------------
  83. // ctkDICOMDatabasePrivate methods
  84. //------------------------------------------------------------------------------
  85. ctkDICOMDatabasePrivate::ctkDICOMDatabasePrivate(ctkDICOMDatabase& o): q_ptr(&o)
  86. {
  87. this->thumbnailGenerator = NULL;
  88. }
  89. //------------------------------------------------------------------------------
  90. void ctkDICOMDatabasePrivate::init(QString databaseFilename)
  91. {
  92. Q_Q(ctkDICOMDatabase);
  93. q->openDatabase(databaseFilename);
  94. }
  95. //------------------------------------------------------------------------------
  96. void ctkDICOMDatabasePrivate::registerCompressionLibraries(){
  97. logger.debug("Register compression libraries");
  98. // Register the JPEG libraries in case we need them
  99. // (registration only happens once, so it's okay to call repeatedly)
  100. // register global JPEG decompression codecs
  101. DJDecoderRegistration::registerCodecs();
  102. // register global JPEG compression codecs
  103. DJEncoderRegistration::registerCodecs();
  104. // register RLE compression codec
  105. DcmRLEEncoderRegistration::registerCodecs();
  106. // register RLE decompression codec
  107. DcmRLEDecoderRegistration::registerCodecs();
  108. }
  109. //------------------------------------------------------------------------------
  110. ctkDICOMDatabasePrivate::~ctkDICOMDatabasePrivate()
  111. {
  112. }
  113. //------------------------------------------------------------------------------
  114. bool ctkDICOMDatabasePrivate::loggedExec(QSqlQuery& query)
  115. {
  116. return (loggedExec(query, QString("")));
  117. }
  118. //------------------------------------------------------------------------------
  119. bool ctkDICOMDatabasePrivate::loggedExec(QSqlQuery& query, const QString& queryString)
  120. {
  121. bool success;
  122. if (queryString.compare(""))
  123. {
  124. success = query.exec(queryString);
  125. }
  126. else
  127. {
  128. success = query.exec();
  129. }
  130. if (!success)
  131. {
  132. QSqlError sqlError = query.lastError();
  133. logger.debug( "SQL failed\n Bad SQL: " + query.lastQuery());
  134. logger.debug( "Error text: " + sqlError.text());
  135. }
  136. else
  137. {
  138. logger.debug( "SQL worked!\n SQL: " + query.lastQuery());
  139. }
  140. return (success);
  141. }
  142. //------------------------------------------------------------------------------
  143. void ctkDICOMDatabase::openDatabase(const QString databaseFile, const QString& connectionName )
  144. {
  145. Q_D(ctkDICOMDatabase);
  146. d->DatabaseFileName = databaseFile;
  147. d->Database = QSqlDatabase::addDatabase("QSQLITE", connectionName);
  148. d->Database.setDatabaseName(databaseFile);
  149. if ( ! (d->Database.open()) )
  150. {
  151. d->LastError = d->Database.lastError().text();
  152. return;
  153. }
  154. if ( d->Database.tables().empty() )
  155. {
  156. if (!initializeDatabase())
  157. {
  158. d->LastError = QString("Unable to initialize DICOM database!");
  159. return;
  160. }
  161. }
  162. if (!isInMemory())
  163. {
  164. QFileSystemWatcher* watcher = new QFileSystemWatcher(QStringList(databaseFile),this);
  165. connect(watcher, SIGNAL(fileChanged(QString)),this, SIGNAL (databaseChanged()) );
  166. }
  167. }
  168. //------------------------------------------------------------------------------
  169. // ctkDICOMDatabase methods
  170. //------------------------------------------------------------------------------
  171. ctkDICOMDatabase::ctkDICOMDatabase(QString databaseFile)
  172. : d_ptr(new ctkDICOMDatabasePrivate(*this))
  173. {
  174. Q_D(ctkDICOMDatabase);
  175. d->registerCompressionLibraries();
  176. d->init(databaseFile);
  177. }
  178. ctkDICOMDatabase::ctkDICOMDatabase(QObject* parent)
  179. : d_ptr(new ctkDICOMDatabasePrivate(*this))
  180. {
  181. Q_UNUSED(parent);
  182. Q_D(ctkDICOMDatabase);
  183. d->registerCompressionLibraries();
  184. }
  185. //------------------------------------------------------------------------------
  186. ctkDICOMDatabase::~ctkDICOMDatabase()
  187. {
  188. }
  189. //----------------------------------------------------------------------------
  190. //------------------------------------------------------------------------------
  191. const QString ctkDICOMDatabase::lastError() const {
  192. Q_D(const ctkDICOMDatabase);
  193. return d->LastError;
  194. }
  195. //------------------------------------------------------------------------------
  196. const QString ctkDICOMDatabase::databaseFilename() const {
  197. Q_D(const ctkDICOMDatabase);
  198. return d->DatabaseFileName;
  199. }
  200. //------------------------------------------------------------------------------
  201. const QString ctkDICOMDatabase::databaseDirectory() const {
  202. QString databaseFile = databaseFilename();
  203. if (!QFileInfo(databaseFile).isAbsolute())
  204. {
  205. databaseFile.prepend(QDir::currentPath() + "/");
  206. }
  207. return QFileInfo ( databaseFile ).absoluteDir().path();
  208. }
  209. //------------------------------------------------------------------------------
  210. const QSqlDatabase& ctkDICOMDatabase::database() const {
  211. Q_D(const ctkDICOMDatabase);
  212. return d->Database;
  213. }
  214. void ctkDICOMDatabase::setThumbnailGenerator(ctkDICOMAbstractThumbnailGenerator *generator){
  215. Q_D(ctkDICOMDatabase);
  216. d->thumbnailGenerator = generator;
  217. }
  218. ctkDICOMAbstractThumbnailGenerator* ctkDICOMDatabase::thumbnailGenerator(){
  219. Q_D(const ctkDICOMDatabase);
  220. return d->thumbnailGenerator;
  221. }
  222. //------------------------------------------------------------------------------
  223. bool ctkDICOMDatabasePrivate::executeScript(const QString script) {
  224. QFile scriptFile(script);
  225. scriptFile.open(QIODevice::ReadOnly);
  226. if ( !scriptFile.isOpen() )
  227. {
  228. qDebug() << "Script file " << script << " could not be opened!\n";
  229. return false;
  230. }
  231. QString sqlCommands( QTextStream(&scriptFile).readAll() );
  232. sqlCommands.replace( '\n', ' ' );
  233. sqlCommands.remove( '\r' );
  234. sqlCommands.replace("; ", ";\n");
  235. QStringList sqlCommandsLines = sqlCommands.split('\n');
  236. QSqlQuery query(Database);
  237. for (QStringList::iterator it = sqlCommandsLines.begin(); it != sqlCommandsLines.end()-1; ++it)
  238. {
  239. if (! (*it).startsWith("--") )
  240. {
  241. qDebug() << *it << "\n";
  242. query.exec(*it);
  243. if (query.lastError().type())
  244. {
  245. qDebug() << "There was an error during execution of the statement: " << (*it);
  246. qDebug() << "Error message: " << query.lastError().text();
  247. return false;
  248. }
  249. }
  250. }
  251. return true;
  252. }
  253. //------------------------------------------------------------------------------
  254. bool ctkDICOMDatabase::initializeDatabase(const char* sqlFileName)
  255. {
  256. Q_D(ctkDICOMDatabase);
  257. return d->executeScript(sqlFileName);
  258. }
  259. //------------------------------------------------------------------------------
  260. void ctkDICOMDatabase::closeDatabase()
  261. {
  262. Q_D(ctkDICOMDatabase);
  263. d->Database.close();
  264. }
  265. //------------------------------------------------------------------------------
  266. QStringList ctkDICOMDatabase::patients()
  267. {
  268. Q_D(ctkDICOMDatabase);
  269. QSqlQuery query(d->Database);
  270. query.prepare ( "SELECT UID FROM Patients" );
  271. query.exec();
  272. QStringList result;
  273. while (query.next())
  274. {
  275. result << query.value(0).toString();
  276. }
  277. return( result );
  278. }
  279. //------------------------------------------------------------------------------
  280. QStringList ctkDICOMDatabase::studiesForPatient(QString dbPatientID)
  281. {
  282. Q_D(ctkDICOMDatabase);
  283. QSqlQuery query(d->Database);
  284. query.prepare ( "SELECT StudyInstanceUID FROM Studies WHERE PatientsUID = ?" );
  285. query.bindValue ( 0, dbPatientID );
  286. query.exec();
  287. QStringList result;
  288. while (query.next())
  289. {
  290. result << query.value(0).toString();
  291. }
  292. return( result );
  293. }
  294. //------------------------------------------------------------------------------
  295. QStringList ctkDICOMDatabase::seriesForStudy(QString studyUID)
  296. {
  297. Q_D(ctkDICOMDatabase);
  298. QSqlQuery query(d->Database);
  299. query.prepare ( "SELECT SeriesInstanceUID FROM Series WHERE StudyInstanceUID=?");
  300. query.bindValue ( 0, studyUID );
  301. query.exec();
  302. QStringList result;
  303. while (query.next())
  304. {
  305. result << query.value(0).toString();
  306. }
  307. return( result );
  308. }
  309. //------------------------------------------------------------------------------
  310. QStringList ctkDICOMDatabase::filesForSeries(QString seriesUID)
  311. {
  312. Q_D(ctkDICOMDatabase);
  313. QSqlQuery query(d->Database);
  314. query.prepare ( "SELECT Filename FROM Images WHERE SeriesInstanceUID=?");
  315. query.bindValue ( 0, seriesUID );
  316. query.exec();
  317. QStringList result;
  318. while (query.next())
  319. {
  320. result << query.value(0).toString();
  321. }
  322. return( result );
  323. }
  324. //------------------------------------------------------------------------------
  325. void ctkDICOMDatabase::loadInstanceHeader (QString sopInstanceUID)
  326. {
  327. Q_D(ctkDICOMDatabase);
  328. QSqlQuery query(d->Database);
  329. query.prepare ( "SELECT Filename FROM Images WHERE SOPInstanceUID=?");
  330. query.bindValue ( 0, sopInstanceUID );
  331. query.exec();
  332. d->LoadedHeader.clear();
  333. if (query.next())
  334. {
  335. QString fileName = query.value(0).toString();
  336. this->loadFileHeader(fileName);
  337. }
  338. return;
  339. }
  340. //------------------------------------------------------------------------------
  341. void ctkDICOMDatabase::loadFileHeader (QString fileName)
  342. {
  343. Q_D(ctkDICOMDatabase);
  344. DcmFileFormat fileFormat;
  345. OFCondition status = fileFormat.loadFile(fileName.toLatin1().data());
  346. if (status.good())
  347. {
  348. DcmDataset *dataset = fileFormat.getDataset();
  349. DcmStack stack;
  350. while (dataset->nextObject(stack, true) == EC_Normal)
  351. {
  352. DcmObject *dO = stack.top();
  353. if (dO->isaString())
  354. {
  355. QString tag = QString("%1,%2").arg(
  356. dO->getGTag(),4,16,QLatin1Char('0')).arg(
  357. dO->getETag(),4,16,QLatin1Char('0'));
  358. std::ostringstream s;
  359. dO->print(s);
  360. d->LoadedHeader[tag] = QString(s.str().c_str());
  361. }
  362. }
  363. }
  364. return;
  365. }
  366. //------------------------------------------------------------------------------
  367. QStringList ctkDICOMDatabase::headerKeys ()
  368. {
  369. Q_D(ctkDICOMDatabase);
  370. return (d->LoadedHeader.keys());
  371. }
  372. //------------------------------------------------------------------------------
  373. QString ctkDICOMDatabase::headerValue (QString key)
  374. {
  375. Q_D(ctkDICOMDatabase);
  376. return (d->LoadedHeader[key]);
  377. }
  378. //------------------------------------------------------------------------------
  379. /*
  380. void ctkDICOMDatabase::insert ( DcmDataset *dataset ) {
  381. this->insert ( dataset, QString() );
  382. }
  383. */
  384. //------------------------------------------------------------------------------
  385. void ctkDICOMDatabase::insert( DcmDataset *dataset, bool storeFile, bool generateThumbnail)
  386. {
  387. if (!dataset)
  388. {
  389. return;
  390. }
  391. ctkDICOMDataset ctkDataset;
  392. ctkDataset.InitializeFromDataset(dataset);
  393. this->insert(ctkDataset,storeFile,generateThumbnail);
  394. }
  395. void ctkDICOMDatabase::insert( const ctkDICOMDataset& ctkDataset, bool storeFile, bool generateThumbnail)
  396. {
  397. Q_D(ctkDICOMDatabase);
  398. // Check to see if the file has already been loaded
  399. // TODO:
  400. // It could make sense to actually remove the dataset and re-add it. This needs the remove
  401. // method we still have to write.
  402. //
  403. QString sopInstanceUID ( ctkDataset.GetElementAsString(DCM_SOPInstanceUID) );
  404. QSqlQuery fileExists ( d->Database );
  405. fileExists.prepare("SELECT InsertTimestamp,Filename FROM Images WHERE SOPInstanceUID == ?");
  406. fileExists.bindValue(0,sopInstanceUID);
  407. fileExists.exec();
  408. if ( fileExists.next() && QFileInfo(fileExists.value(1).toString()).lastModified() < QDateTime::fromString(fileExists.value(0).toString(),Qt::ISODate) )
  409. {
  410. logger.debug ( "File " + fileExists.value(1).toString() + " already added" );
  411. return;
  412. }
  413. //If the following fields can not be evaluated, cancel evaluation of the DICOM file
  414. QString patientsName(ctkDataset.GetElementAsString(DCM_PatientName) );
  415. QString studyInstanceUID(ctkDataset.GetElementAsString(DCM_StudyInstanceUID) );
  416. QString seriesInstanceUID(ctkDataset.GetElementAsString(DCM_SeriesInstanceUID) );
  417. QString patientID(ctkDataset.GetElementAsString(DCM_PatientID) );
  418. if ( patientsName.isEmpty() || studyInstanceUID.isEmpty() || seriesInstanceUID.isEmpty() || patientID.isEmpty() )
  419. {
  420. logger.error("Dataset is missing necessary information!");
  421. return;
  422. }
  423. QString patientsBirthDate(ctkDataset.GetElementAsString(DCM_PatientBirthDate) );
  424. QString patientsBirthTime(ctkDataset.GetElementAsString(DCM_PatientBirthTime) );
  425. QString patientsSex(ctkDataset.GetElementAsString(DCM_PatientSex) );
  426. QString patientsAge(ctkDataset.GetElementAsString(DCM_PatientAge) );
  427. QString patientComments(ctkDataset.GetElementAsString(DCM_PatientComments) );
  428. QString studyID(ctkDataset.GetElementAsString(DCM_StudyID) );
  429. QString studyDate(ctkDataset.GetElementAsString(DCM_StudyDate) );
  430. QString studyTime(ctkDataset.GetElementAsString(DCM_StudyTime) );
  431. QString accessionNumber(ctkDataset.GetElementAsString(DCM_AccessionNumber) );
  432. QString modalitiesInStudy(ctkDataset.GetElementAsString(DCM_ModalitiesInStudy) );
  433. QString institutionName(ctkDataset.GetElementAsString(DCM_InstitutionName) );
  434. QString performingPhysiciansName(ctkDataset.GetElementAsString(DCM_PerformingPhysicianName) );
  435. QString referringPhysician(ctkDataset.GetElementAsString(DCM_ReferringPhysicianName) );
  436. QString studyDescription(ctkDataset.GetElementAsString(DCM_StudyDescription) );
  437. QString seriesDate(ctkDataset.GetElementAsString(DCM_SeriesDate) );
  438. QString seriesTime(ctkDataset.GetElementAsString(DCM_SeriesTime) );
  439. QString seriesDescription(ctkDataset.GetElementAsString(DCM_SeriesDescription) );
  440. QString bodyPartExamined(ctkDataset.GetElementAsString(DCM_BodyPartExamined) );
  441. QString frameOfReferenceUID(ctkDataset.GetElementAsString(DCM_FrameOfReferenceUID) );
  442. QString contrastAgent(ctkDataset.GetElementAsString(DCM_ContrastBolusAgent) );
  443. QString scanningSequence(ctkDataset.GetElementAsString(DCM_ScanningSequence) );
  444. long seriesNumber(ctkDataset.GetElementAsInteger(DCM_SeriesNumber) );
  445. long acquisitionNumber(ctkDataset.GetElementAsInteger(DCM_AcquisitionNumber) );
  446. long echoNumber(ctkDataset.GetElementAsInteger(DCM_EchoNumbers) );
  447. long temporalPosition(ctkDataset.GetElementAsInteger(DCM_TemporalPositionIdentifier) );
  448. // store the file if the database is not in memomry
  449. // TODO: if we are called from insert(file) we
  450. // have to do something else
  451. //
  452. QString filename;
  453. if ( storeFile && !this->isInMemory() )
  454. {
  455. QString destinationDirectoryName = databaseDirectory() + "/dicom/";
  456. QDir destinationDir(destinationDirectoryName);
  457. QString studySeriesDirectory = studyInstanceUID + "/" + seriesInstanceUID;
  458. destinationDir.mkpath(studySeriesDirectory);
  459. filename = databaseDirectory() + "/dicom/" + pathForDataset(ctkDataset);
  460. logger.debug ( "Saving file: " + filename );
  461. if ( !ctkDataset.SaveToFile( filename) )
  462. {
  463. logger.error ( "Error saving file: " + filename );
  464. return;
  465. }
  466. }
  467. QSqlQuery checkPatientExistsQuery(d->Database);
  468. //The dbPatientID is a unique number within the database, generated by the sqlite autoincrement
  469. //The patientID is the (non-unique) DICOM patient id
  470. int dbPatientID = -1;
  471. if ( patientID != "" && patientsName != "" )
  472. {
  473. //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
  474. if( d->lastPatientID != patientID || d->lastPatientsBirthDate != patientsBirthDate || d->lastPatientsName != patientsName )
  475. {
  476. // Ok, something is different from last insert, let's insert him if he's not
  477. // already in the db.
  478. //
  479. // Check if patient is already present in the db
  480. // TODO: maybe add birthdate check for extra safety
  481. checkPatientExistsQuery.prepare ( "SELECT * FROM Patients WHERE PatientID = ? AND PatientsName = ?" );
  482. checkPatientExistsQuery.bindValue ( 0, patientID );
  483. checkPatientExistsQuery.bindValue ( 1, patientsName );
  484. d->loggedExec(checkPatientExistsQuery);
  485. if (checkPatientExistsQuery.next())
  486. {
  487. // we found him
  488. dbPatientID = checkPatientExistsQuery.value(checkPatientExistsQuery.record().indexOf("UID")).toInt();
  489. }
  490. else
  491. {
  492. // Insert it
  493. QSqlQuery insertPatientStatement ( d->Database );
  494. insertPatientStatement.prepare ( "INSERT INTO Patients ('UID', 'PatientsName', 'PatientID', 'PatientsBirthDate', 'PatientsBirthTime', 'PatientsSex', 'PatientsAge', 'PatientsComments' ) values ( NULL, ?, ?, ?, ?, ?, ?, ? )" );
  495. insertPatientStatement.bindValue ( 0, patientsName );
  496. insertPatientStatement.bindValue ( 1, patientID );
  497. insertPatientStatement.bindValue ( 2, patientsBirthDate );
  498. insertPatientStatement.bindValue ( 3, patientsBirthTime );
  499. insertPatientStatement.bindValue ( 4, patientsSex );
  500. // TODO: shift patient's age to study, since this is not a patient level attribute in images
  501. // insertPatientStatement.bindValue ( 5, patientsAge );
  502. insertPatientStatement.bindValue ( 6, patientComments );
  503. d->loggedExec(insertPatientStatement);
  504. dbPatientID = insertPatientStatement.lastInsertId().toInt();
  505. logger.debug ( "New patient inserted: " + QString().setNum ( dbPatientID ) );
  506. }
  507. /// keep this for the next image
  508. d->lastPatientUID = dbPatientID;
  509. d->lastPatientID = patientID;
  510. d->lastPatientsBirthDate = patientsBirthDate;
  511. d->lastPatientsName = patientsName;
  512. }
  513. // Patient is in now. Let's continue with the study
  514. if ( studyInstanceUID != "" && d->lastStudyInstanceUID != studyInstanceUID )
  515. {
  516. QSqlQuery checkStudyExistsQuery (d->Database);
  517. checkStudyExistsQuery.prepare ( "SELECT * FROM Studies WHERE StudyInstanceUID = ?" );
  518. checkStudyExistsQuery.bindValue ( 0, studyInstanceUID );
  519. checkStudyExistsQuery.exec();
  520. if(!checkStudyExistsQuery.next())
  521. {
  522. QSqlQuery insertStudyStatement ( d->Database );
  523. insertStudyStatement.prepare ( "INSERT INTO Studies ( 'StudyInstanceUID', 'PatientsUID', 'StudyID', 'StudyDate', 'StudyTime', 'AccessionNumber', 'ModalitiesInStudy', 'InstitutionName', 'ReferringPhysician', 'PerformingPhysiciansName', 'StudyDescription' ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )" );
  524. insertStudyStatement.bindValue ( 0, studyInstanceUID );
  525. insertStudyStatement.bindValue ( 1, dbPatientID );
  526. insertStudyStatement.bindValue ( 2, studyID );
  527. insertStudyStatement.bindValue ( 3, QDate::fromString ( studyDate, "yyyyMMdd" ) );
  528. insertStudyStatement.bindValue ( 4, studyTime );
  529. insertStudyStatement.bindValue ( 5, accessionNumber );
  530. insertStudyStatement.bindValue ( 6, modalitiesInStudy );
  531. insertStudyStatement.bindValue ( 7, institutionName );
  532. insertStudyStatement.bindValue ( 8, referringPhysician );
  533. insertStudyStatement.bindValue ( 9, performingPhysiciansName );
  534. insertStudyStatement.bindValue ( 10, studyDescription );
  535. if ( !insertStudyStatement.exec() )
  536. {
  537. logger.error ( "Error executing statament: " + insertStudyStatement.lastQuery() + " Error: " + insertStudyStatement.lastError().text() );
  538. }
  539. else
  540. {
  541. d->lastStudyInstanceUID = studyInstanceUID;
  542. }
  543. }
  544. }
  545. if ( seriesInstanceUID != "" && seriesInstanceUID != d->lastSeriesInstanceUID )
  546. {
  547. QSqlQuery checkSeriesExistsQuery (d->Database);
  548. checkSeriesExistsQuery.prepare ( "SELECT * FROM Series WHERE SeriesInstanceUID = ?" );
  549. checkSeriesExistsQuery.bindValue ( 0, seriesInstanceUID );
  550. logger.warn ( "Statement: " + checkSeriesExistsQuery.lastQuery() );
  551. d->loggedExec(checkSeriesExistsQuery);
  552. if(!checkSeriesExistsQuery.next())
  553. {
  554. QSqlQuery insertSeriesStatement ( d->Database );
  555. insertSeriesStatement.prepare ( "INSERT INTO Series ( 'SeriesInstanceUID', 'StudyInstanceUID', 'SeriesNumber', 'SeriesDate', 'SeriesTime', 'SeriesDescription', 'BodyPartExamined', 'FrameOfReferenceUID', 'AcquisitionNumber', 'ContrastAgent', 'ScanningSequence', 'EchoNumber', 'TemporalPosition' ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )" );
  556. insertSeriesStatement.bindValue ( 0, seriesInstanceUID );
  557. insertSeriesStatement.bindValue ( 1, studyInstanceUID );
  558. insertSeriesStatement.bindValue ( 2, static_cast<int>(seriesNumber) );
  559. insertSeriesStatement.bindValue ( 3, seriesDate );
  560. insertSeriesStatement.bindValue ( 4, QDate::fromString ( seriesTime, "yyyyMMdd" ) );
  561. insertSeriesStatement.bindValue ( 5, seriesDescription );
  562. insertSeriesStatement.bindValue ( 6, bodyPartExamined );
  563. insertSeriesStatement.bindValue ( 7, frameOfReferenceUID );
  564. insertSeriesStatement.bindValue ( 8, static_cast<int>(acquisitionNumber) );
  565. insertSeriesStatement.bindValue ( 9, contrastAgent );
  566. insertSeriesStatement.bindValue ( 10, scanningSequence );
  567. insertSeriesStatement.bindValue ( 11, static_cast<int>(echoNumber) );
  568. insertSeriesStatement.bindValue ( 12, static_cast<int>(temporalPosition) );
  569. if ( !insertSeriesStatement.exec() )
  570. {
  571. logger.error ( "Error executing statament: " + insertSeriesStatement.lastQuery() + " Error: " + insertSeriesStatement.lastError().text() );
  572. d->lastSeriesInstanceUID = "";
  573. }
  574. else
  575. {
  576. d->lastSeriesInstanceUID = seriesInstanceUID;
  577. }
  578. }
  579. }
  580. // TODO: what to do with imported files
  581. //
  582. if ( !filename.isEmpty() )
  583. {
  584. QSqlQuery checkImageExistsQuery (d->Database);
  585. checkImageExistsQuery.prepare ( "SELECT * FROM Images WHERE Filename = ?" );
  586. checkImageExistsQuery.bindValue ( 0, filename );
  587. checkImageExistsQuery.exec();
  588. if(!checkImageExistsQuery.next())
  589. {
  590. QSqlQuery insertImageStatement ( d->Database );
  591. insertImageStatement.prepare ( "INSERT INTO Images ( 'SOPInstanceUID', 'Filename', 'SeriesInstanceUID', 'InsertTimestamp' ) VALUES ( ?, ?, ?, ? )" );
  592. insertImageStatement.bindValue ( 0, sopInstanceUID );
  593. insertImageStatement.bindValue ( 1, filename );
  594. insertImageStatement.bindValue ( 2, seriesInstanceUID );
  595. insertImageStatement.bindValue ( 3, QDateTime::currentDateTime() );
  596. insertImageStatement.exec();
  597. }
  598. }
  599. /**
  600. * old move/copy code from indexer insert
  601. *
  602. QString studySeriesDirectory = studyInstanceUID + "/" + seriesInstanceUID;
  603. //----------------------------------
  604. //Move file to destination directory
  605. //----------------------------------
  606. QString finalFilePath(filePath);
  607. if (!destinationDirectoryName.isEmpty())
  608. {
  609. QFile currentFile( filePath );
  610. QDir destinationDir(destinationDirectoryName + "/dicom");
  611. finalFilePath = sopInstanceUID;
  612. if (createHierarchy)
  613. {
  614. destinationDir.mkpath(studySeriesDirectory);
  615. finalFilePath.prepend( destinationDir.absolutePath() + "/" + studySeriesDirectory + "/" );
  616. }
  617. currentFile.copy(finalFilePath);
  618. logger.debug( "Copy file from: " + filePath );
  619. logger.debug( "Copy file to : " + finalFilePath );
  620. }
  621. logger.debug(QString("finalFilePath: ") + finalFilePath);
  622. */
  623. if(generateThumbnail){
  624. if(d->thumbnailGenerator)
  625. {
  626. QString studySeriesDirectory = studyInstanceUID + "/" + seriesInstanceUID;
  627. //Create thumbnail here
  628. QString thumbnailPath = databaseDirectory() +
  629. "/thumbs/" + this->pathForDataset(ctkDataset) + ".png";
  630. //studyInstanceUID + "/" +
  631. //seriesInstanceUID + "/" +
  632. //sopInstanceUID + ".png";
  633. QFileInfo thumbnailInfo(thumbnailPath);
  634. if(!(thumbnailInfo.exists() && (thumbnailInfo.lastModified() > QFileInfo(filename).lastModified()))){
  635. QDir(databaseDirectory() + "/thumbs/").mkpath(studySeriesDirectory);
  636. DicomImage dcmImage(QDir::toNativeSeparators(filename).toAscii());
  637. d->thumbnailGenerator->generateThumbnail(&dcmImage, thumbnailPath);
  638. }
  639. }
  640. }
  641. if (isInMemory())
  642. {
  643. emit databaseChanged();
  644. }
  645. }
  646. }
  647. //------------------------------------------------------------------------------
  648. void ctkDICOMDatabase::insert ( const QString& filePath, bool storeFile, bool generateThumbnail, bool createHierarchy, const QString& destinationDirectoryName)
  649. {
  650. /// first we check if the file is already in the database
  651. if (fileExistsAndUpToDate(filePath))
  652. {
  653. logger.debug( "File " + filePath + " already added.");
  654. return;
  655. }
  656. logger.debug( "Processing " + filePath );
  657. std::string filename = filePath.toStdString();
  658. DcmFileFormat fileformat;
  659. ctkDICOMDataset ctkDataset;
  660. ctkDataset.InitializeFromFile(filePath);
  661. this->insert( ctkDataset, storeFile, generateThumbnail );
  662. }
  663. bool ctkDICOMDatabase::fileExistsAndUpToDate(const QString& filePath)
  664. {
  665. Q_D(ctkDICOMDatabase);
  666. bool result(false);
  667. QSqlQuery check_filename_query(database());
  668. check_filename_query.prepare("SELECT InsertTimestamp FROM Images WHERE Filename == ?");
  669. check_filename_query.bindValue(0,filePath);
  670. d->loggedExec(check_filename_query);
  671. if (
  672. check_filename_query.next() &&
  673. QFileInfo(filePath).lastModified() < QDateTime::fromString(check_filename_query.value(0).toString(),Qt::ISODate)
  674. )
  675. {
  676. result = true;
  677. }
  678. check_filename_query.finish();
  679. return result;
  680. }
  681. bool ctkDICOMDatabase::isOpen() const
  682. {
  683. Q_D(const ctkDICOMDatabase);
  684. return d->Database.isOpen();
  685. }
  686. bool ctkDICOMDatabase::isInMemory() const
  687. {
  688. Q_D(const ctkDICOMDatabase);
  689. return d->DatabaseFileName == ":memory:";
  690. }
  691. QString ctkDICOMDatabase::pathForDataset( const ctkDICOMDataset& ctkDataset)
  692. {
  693. // TODO: this is not related to the database
  694. // could be static, is it necessary?
  695. if ( !ctkDataset.IsInitialized() )
  696. {
  697. return QString();
  698. }
  699. QString studyInstanceUID(ctkDataset.GetElementAsString(DCM_StudyInstanceUID) );
  700. QString seriesInstanceUID(ctkDataset.GetElementAsString(DCM_SeriesInstanceUID) );
  701. QString sopInstanceUID ( ctkDataset.GetElementAsString(DCM_SOPInstanceUID) );
  702. return studyInstanceUID + "/" + seriesInstanceUID + "/" + sopInstanceUID;
  703. }