ctkDICOMDatabase.cpp 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908
  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 <QDate>
  17. #include <QDebug>
  18. #include <QFile>
  19. #include <QFileInfo>
  20. #include <QFileSystemWatcher>
  21. #include <QSet>
  22. #include <QSqlError>
  23. #include <QSqlQuery>
  24. #include <QSqlRecord>
  25. #include <QStringList>
  26. #include <QUuid>
  27. #include <QVariant>
  28. // ctkDICOM includes
  29. #include "ctkDICOMDatabase.h"
  30. #include "ctkDICOMAbstractThumbnailGenerator.h"
  31. #include "ctkDICOMItem.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 <dcmtk/dcmimgle/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. // Flag for tag cache to avoid repeated serarches for
  51. // tags that do no exist.
  52. static QString TagNotInInstance("__TAG_NOT_IN_INSTANCE__");
  53. // Flag for tag cache indicating that the value
  54. // really is the empty string
  55. static QString ValueIsEmptyString("__VALUE_IS_EMPTY_STRING__");
  56. //------------------------------------------------------------------------------
  57. class ctkDICOMDatabasePrivate
  58. {
  59. Q_DECLARE_PUBLIC(ctkDICOMDatabase);
  60. protected:
  61. ctkDICOMDatabase* const q_ptr;
  62. public:
  63. ctkDICOMDatabasePrivate(ctkDICOMDatabase&);
  64. ~ctkDICOMDatabasePrivate();
  65. void init(QString databaseFile);
  66. void registerCompressionLibraries();
  67. bool executeScript(const QString script);
  68. ///
  69. /// \brief runs a query and prints debug output of status
  70. ///
  71. bool loggedExec(QSqlQuery& query);
  72. bool loggedExec(QSqlQuery& query, const QString& queryString);
  73. bool loggedExecBatch(QSqlQuery& query);
  74. bool LoggedExecVerbose;
  75. ///
  76. /// \brief group several inserts into a single transaction
  77. ///
  78. void beginTransaction();
  79. void endTransaction();
  80. // dataset must be set always
  81. // filePath has to be set if this is an import of an actual file
  82. void insert ( const ctkDICOMItem& ctkDataset, const QString& filePath, bool storeFile = true, bool generateThumbnail = true);
  83. ///
  84. /// copy the complete list of files to an extra table
  85. ///
  86. void createBackupFileList();
  87. ///
  88. /// remove the extra table containing the backup
  89. ///
  90. void removeBackupFileList();
  91. ///
  92. /// get all Filename values from table
  93. QStringList filenames(QString table);
  94. /// Name of the database file (i.e. for SQLITE the sqlite file)
  95. QString DatabaseFileName;
  96. QString LastError;
  97. QSqlDatabase Database;
  98. QMap<QString, QString> LoadedHeader;
  99. ctkDICOMAbstractThumbnailGenerator* thumbnailGenerator;
  100. /// these are for optimizing the import of image sequences
  101. /// since most information are identical for all slices
  102. QString LastPatientID;
  103. QString LastPatientsName;
  104. QString LastPatientsBirthDate;
  105. QString LastStudyInstanceUID;
  106. QString LastSeriesInstanceUID;
  107. int LastPatientUID;
  108. /// resets the variables to new inserts won't be fooled by leftover values
  109. void resetLastInsertedValues();
  110. /// tagCache table has been checked to exist
  111. bool TagCacheVerified;
  112. /// tag cache has independent database to avoid locking issue
  113. /// with other access to the database which need to be
  114. /// reading while the tag cache is writing
  115. QSqlDatabase TagCacheDatabase;
  116. QString TagCacheDatabaseFilename;
  117. QStringList TagsToPrecache;
  118. bool openTagCacheDatabase();
  119. void precacheTags( const QString sopInstanceUID );
  120. int insertPatient(const ctkDICOMItem& ctkDataset);
  121. void insertStudy(const ctkDICOMItem& ctkDataset, int dbPatientID);
  122. void insertSeries( const ctkDICOMItem& ctkDataset, QString studyInstanceUID);
  123. };
  124. //------------------------------------------------------------------------------
  125. // ctkDICOMDatabasePrivate methods
  126. //------------------------------------------------------------------------------
  127. ctkDICOMDatabasePrivate::ctkDICOMDatabasePrivate(ctkDICOMDatabase& o): q_ptr(&o)
  128. {
  129. this->thumbnailGenerator = NULL;
  130. this->LoggedExecVerbose = false;
  131. this->TagCacheVerified = false;
  132. this->resetLastInsertedValues();
  133. }
  134. //------------------------------------------------------------------------------
  135. void ctkDICOMDatabasePrivate::resetLastInsertedValues()
  136. {
  137. this->LastPatientID = QString("");
  138. this->LastPatientsName = QString("");
  139. this->LastPatientsBirthDate = QString("");
  140. this->LastStudyInstanceUID = QString("");
  141. this->LastSeriesInstanceUID = QString("");
  142. this->LastPatientUID = -1;
  143. }
  144. //------------------------------------------------------------------------------
  145. void ctkDICOMDatabasePrivate::init(QString databaseFilename)
  146. {
  147. Q_Q(ctkDICOMDatabase);
  148. q->openDatabase(databaseFilename);
  149. }
  150. //------------------------------------------------------------------------------
  151. void ctkDICOMDatabasePrivate::registerCompressionLibraries(){
  152. // Register the JPEG libraries in case we need them
  153. // (registration only happens once, so it's okay to call repeatedly)
  154. // register global JPEG decompression codecs
  155. DJDecoderRegistration::registerCodecs();
  156. // register global JPEG compression codecs
  157. DJEncoderRegistration::registerCodecs();
  158. // register RLE compression codec
  159. DcmRLEEncoderRegistration::registerCodecs();
  160. // register RLE decompression codec
  161. DcmRLEDecoderRegistration::registerCodecs();
  162. }
  163. //------------------------------------------------------------------------------
  164. ctkDICOMDatabasePrivate::~ctkDICOMDatabasePrivate()
  165. {
  166. }
  167. //------------------------------------------------------------------------------
  168. bool ctkDICOMDatabasePrivate::loggedExec(QSqlQuery& query)
  169. {
  170. return (loggedExec(query, QString("")));
  171. }
  172. //------------------------------------------------------------------------------
  173. bool ctkDICOMDatabasePrivate::loggedExec(QSqlQuery& query, const QString& queryString)
  174. {
  175. bool success;
  176. if (queryString.compare(""))
  177. {
  178. success = query.exec(queryString);
  179. }
  180. else
  181. {
  182. success = query.exec();
  183. }
  184. if (!success)
  185. {
  186. QSqlError sqlError = query.lastError();
  187. logger.debug( "SQL failed\n Bad SQL: " + query.lastQuery());
  188. logger.debug( "Error text: " + sqlError.text());
  189. }
  190. else
  191. {
  192. if (LoggedExecVerbose)
  193. {
  194. logger.debug( "SQL worked!\n SQL: " + query.lastQuery());
  195. }
  196. }
  197. return (success);
  198. }
  199. //------------------------------------------------------------------------------
  200. bool ctkDICOMDatabasePrivate::loggedExecBatch(QSqlQuery& query)
  201. {
  202. bool success;
  203. success = query.execBatch();
  204. if (!success)
  205. {
  206. QSqlError sqlError = query.lastError();
  207. logger.debug( "SQL failed\n Bad SQL: " + query.lastQuery());
  208. logger.debug( "Error text: " + sqlError.text());
  209. }
  210. else
  211. {
  212. if (LoggedExecVerbose)
  213. {
  214. logger.debug( "SQL worked!\n SQL: " + query.lastQuery());
  215. }
  216. }
  217. return (success);
  218. }
  219. //------------------------------------------------------------------------------
  220. void ctkDICOMDatabasePrivate::beginTransaction()
  221. {
  222. QSqlQuery transaction( this->Database );
  223. transaction.prepare( "BEGIN TRANSACTION" );
  224. transaction.exec();
  225. }
  226. //------------------------------------------------------------------------------
  227. void ctkDICOMDatabasePrivate::endTransaction()
  228. {
  229. QSqlQuery transaction( this->Database );
  230. transaction.prepare( "END TRANSACTION" );
  231. transaction.exec();
  232. }
  233. //------------------------------------------------------------------------------
  234. void ctkDICOMDatabasePrivate::createBackupFileList()
  235. {
  236. QSqlQuery query(this->Database);
  237. loggedExec(query, "CREATE TABLE IF NOT EXISTS main.Filenames_backup (Filename TEXT PRIMARY KEY NOT NULL )" );
  238. loggedExec(query, "INSERT INTO Filenames_backup SELECT Filename FROM Images;" );
  239. }
  240. //------------------------------------------------------------------------------
  241. void ctkDICOMDatabasePrivate::removeBackupFileList()
  242. {
  243. QSqlQuery query(this->Database);
  244. loggedExec(query, "DROP TABLE main.Filenames_backup; " );
  245. }
  246. //------------------------------------------------------------------------------
  247. void ctkDICOMDatabase::openDatabase(const QString databaseFile, const QString& connectionName )
  248. {
  249. Q_D(ctkDICOMDatabase);
  250. d->DatabaseFileName = databaseFile;
  251. QString verifiedConnectionName = connectionName;
  252. if (verifiedConnectionName.isEmpty())
  253. {
  254. verifiedConnectionName = QUuid::createUuid().toString();
  255. }
  256. d->Database = QSqlDatabase::addDatabase("QSQLITE", verifiedConnectionName);
  257. d->Database.setDatabaseName(databaseFile);
  258. if ( ! (d->Database.open()) )
  259. {
  260. d->LastError = d->Database.lastError().text();
  261. return;
  262. }
  263. if ( d->Database.tables().empty() )
  264. {
  265. if (!initializeDatabase())
  266. {
  267. d->LastError = QString("Unable to initialize DICOM database!");
  268. return;
  269. }
  270. }
  271. d->resetLastInsertedValues();
  272. if (!isInMemory())
  273. {
  274. QFileSystemWatcher* watcher = new QFileSystemWatcher(QStringList(databaseFile),this);
  275. connect(watcher, SIGNAL(fileChanged(QString)),this, SIGNAL (databaseChanged()) );
  276. }
  277. //Disable synchronous writing to make modifications faster
  278. {
  279. QSqlQuery pragmaSyncQuery(d->Database);
  280. pragmaSyncQuery.exec("PRAGMA synchronous = OFF");
  281. pragmaSyncQuery.finish();
  282. }
  283. // set up the tag cache for use later
  284. QFileInfo fileInfo(d->DatabaseFileName);
  285. d->TagCacheDatabaseFilename = QString( fileInfo.dir().path() + "/ctkDICOMTagCache.sql" );
  286. d->TagCacheVerified = false;
  287. if ( !this->tagCacheExists() )
  288. {
  289. this->initializeTagCache();
  290. }
  291. }
  292. //------------------------------------------------------------------------------
  293. // ctkDICOMDatabase methods
  294. //------------------------------------------------------------------------------
  295. ctkDICOMDatabase::ctkDICOMDatabase(QString databaseFile)
  296. : d_ptr(new ctkDICOMDatabasePrivate(*this))
  297. {
  298. Q_D(ctkDICOMDatabase);
  299. d->registerCompressionLibraries();
  300. d->init(databaseFile);
  301. }
  302. ctkDICOMDatabase::ctkDICOMDatabase(QObject* parent)
  303. : d_ptr(new ctkDICOMDatabasePrivate(*this))
  304. {
  305. Q_UNUSED(parent);
  306. Q_D(ctkDICOMDatabase);
  307. d->registerCompressionLibraries();
  308. }
  309. //------------------------------------------------------------------------------
  310. ctkDICOMDatabase::~ctkDICOMDatabase()
  311. {
  312. }
  313. //----------------------------------------------------------------------------
  314. //------------------------------------------------------------------------------
  315. const QString ctkDICOMDatabase::lastError() const {
  316. Q_D(const ctkDICOMDatabase);
  317. return d->LastError;
  318. }
  319. //------------------------------------------------------------------------------
  320. const QString ctkDICOMDatabase::databaseFilename() const {
  321. Q_D(const ctkDICOMDatabase);
  322. return d->DatabaseFileName;
  323. }
  324. //------------------------------------------------------------------------------
  325. const QString ctkDICOMDatabase::databaseDirectory() const {
  326. QString databaseFile = databaseFilename();
  327. if (!QFileInfo(databaseFile).isAbsolute())
  328. {
  329. databaseFile.prepend(QDir::currentPath() + "/");
  330. }
  331. return QFileInfo ( databaseFile ).absoluteDir().path();
  332. }
  333. //------------------------------------------------------------------------------
  334. const QSqlDatabase& ctkDICOMDatabase::database() const {
  335. Q_D(const ctkDICOMDatabase);
  336. return d->Database;
  337. }
  338. //------------------------------------------------------------------------------
  339. void ctkDICOMDatabase::setThumbnailGenerator(ctkDICOMAbstractThumbnailGenerator *generator){
  340. Q_D(ctkDICOMDatabase);
  341. d->thumbnailGenerator = generator;
  342. }
  343. //------------------------------------------------------------------------------
  344. ctkDICOMAbstractThumbnailGenerator* ctkDICOMDatabase::thumbnailGenerator(){
  345. Q_D(const ctkDICOMDatabase);
  346. return d->thumbnailGenerator;
  347. }
  348. //------------------------------------------------------------------------------
  349. bool ctkDICOMDatabasePrivate::executeScript(const QString script) {
  350. QFile scriptFile(script);
  351. scriptFile.open(QIODevice::ReadOnly);
  352. if ( !scriptFile.isOpen() )
  353. {
  354. qDebug() << "Script file " << script << " could not be opened!\n";
  355. return false;
  356. }
  357. QString sqlCommands( QTextStream(&scriptFile).readAll() );
  358. sqlCommands.replace( '\n', ' ' );
  359. sqlCommands.remove( '\r' );
  360. sqlCommands.replace("; ", ";\n");
  361. QStringList sqlCommandsLines = sqlCommands.split('\n');
  362. QSqlQuery query(Database);
  363. for (QStringList::iterator it = sqlCommandsLines.begin(); it != sqlCommandsLines.end()-1; ++it)
  364. {
  365. if (! (*it).startsWith("--") )
  366. {
  367. if (LoggedExecVerbose)
  368. {
  369. qDebug() << *it << "\n";
  370. }
  371. query.exec(*it);
  372. if (query.lastError().type())
  373. {
  374. qDebug() << "There was an error during execution of the statement: " << (*it);
  375. qDebug() << "Error message: " << query.lastError().text();
  376. return false;
  377. }
  378. }
  379. }
  380. return true;
  381. }
  382. //------------------------------------------------------------------------------
  383. QStringList ctkDICOMDatabasePrivate::filenames(QString table)
  384. {
  385. /// get all filenames from the database
  386. QSqlQuery allFilesQuery(this->Database);
  387. QStringList allFileNames;
  388. loggedExec(allFilesQuery,QString("SELECT Filename from %1 ;").arg(table) );
  389. while (allFilesQuery.next())
  390. {
  391. allFileNames << allFilesQuery.value(0).toString();
  392. }
  393. return allFileNames;
  394. }
  395. //------------------------------------------------------------------------------
  396. bool ctkDICOMDatabase::initializeDatabase(const char* sqlFileName)
  397. {
  398. Q_D(ctkDICOMDatabase);
  399. d->resetLastInsertedValues();
  400. // remove any existing schema info - this handles the case where an
  401. // old schema should be loaded for testing.
  402. QSqlQuery dropSchemaInfo(d->Database);
  403. d->loggedExec( dropSchemaInfo, QString("DROP TABLE IF EXISTS 'SchemaInfo';") );
  404. return d->executeScript(sqlFileName);
  405. }
  406. //------------------------------------------------------------------------------
  407. QString ctkDICOMDatabase::schemaVersionLoaded()
  408. {
  409. Q_D(ctkDICOMDatabase);
  410. /// look for the version info in the database
  411. QSqlQuery versionQuery(d->Database);
  412. if ( !d->loggedExec( versionQuery, QString("SELECT Version from SchemaInfo;") ) )
  413. {
  414. return QString("");
  415. }
  416. if (versionQuery.next())
  417. {
  418. return versionQuery.value(0).toString();
  419. }
  420. return QString("");
  421. }
  422. //------------------------------------------------------------------------------
  423. QString ctkDICOMDatabase::schemaVersion()
  424. {
  425. // When changing schema version:
  426. // * make sure this matches the Version value in the
  427. // SchemaInfo table defined in Resources/dicom-schema.sql
  428. // * make sure the 'Images' contains a 'Filename' column
  429. // so that the ctkDICOMDatabasePrivate::filenames method
  430. // still works.
  431. //
  432. return QString("0.5.3");
  433. };
  434. //------------------------------------------------------------------------------
  435. bool ctkDICOMDatabase::updateSchemaIfNeeded(const char* schemaFile)
  436. {
  437. if ( schemaVersionLoaded() != schemaVersion() )
  438. {
  439. return this->updateSchema(schemaFile);
  440. }
  441. else
  442. {
  443. emit schemaUpdateStarted(0);
  444. emit schemaUpdated();
  445. return false;
  446. }
  447. }
  448. //------------------------------------------------------------------------------
  449. bool ctkDICOMDatabase::updateSchema(const char* schemaFile)
  450. {
  451. // backup filelist
  452. // reinit with the new schema
  453. // reinsert everything
  454. Q_D(ctkDICOMDatabase);
  455. d->createBackupFileList();
  456. d->resetLastInsertedValues();
  457. this->initializeDatabase(schemaFile);
  458. QStringList allFiles = d->filenames("Filenames_backup");
  459. emit schemaUpdateStarted(allFiles.length());
  460. int progressValue = 0;
  461. foreach(QString file, allFiles)
  462. {
  463. emit schemaUpdateProgress(progressValue);
  464. emit schemaUpdateProgress(file);
  465. // TODO: use QFuture
  466. this->insert(file,false,false,true);
  467. progressValue++;
  468. }
  469. // TODO: check better that everything is ok
  470. d->removeBackupFileList();
  471. emit schemaUpdated();
  472. return true;
  473. }
  474. //------------------------------------------------------------------------------
  475. void ctkDICOMDatabase::closeDatabase()
  476. {
  477. Q_D(ctkDICOMDatabase);
  478. d->Database.close();
  479. d->TagCacheDatabase.close();
  480. }
  481. //
  482. // Patient/study/series convenience methods
  483. //
  484. //------------------------------------------------------------------------------
  485. QStringList ctkDICOMDatabase::patients()
  486. {
  487. Q_D(ctkDICOMDatabase);
  488. QSqlQuery query(d->Database);
  489. query.prepare ( "SELECT UID FROM Patients" );
  490. query.exec();
  491. QStringList result;
  492. while (query.next())
  493. {
  494. result << query.value(0).toString();
  495. }
  496. return( result );
  497. }
  498. //------------------------------------------------------------------------------
  499. QStringList ctkDICOMDatabase::studiesForPatient(QString dbPatientID)
  500. {
  501. Q_D(ctkDICOMDatabase);
  502. QSqlQuery query(d->Database);
  503. query.prepare ( "SELECT StudyInstanceUID FROM Studies WHERE PatientsUID = ?" );
  504. query.bindValue ( 0, dbPatientID );
  505. query.exec();
  506. QStringList result;
  507. while (query.next())
  508. {
  509. result << query.value(0).toString();
  510. }
  511. return( result );
  512. }
  513. //------------------------------------------------------------------------------
  514. QString ctkDICOMDatabase::studyForSeries(QString seriesUID)
  515. {
  516. Q_D(ctkDICOMDatabase);
  517. QSqlQuery query(d->Database);
  518. query.prepare ( "SELECT StudyInstanceUID FROM Series WHERE SeriesInstanceUID= ?" );
  519. query.bindValue ( 0, seriesUID);
  520. query.exec();
  521. QString result;
  522. if (query.next())
  523. {
  524. result = query.value(0).toString();
  525. }
  526. return( result );
  527. }
  528. //------------------------------------------------------------------------------
  529. QString ctkDICOMDatabase::patientForStudy(QString studyUID)
  530. {
  531. Q_D(ctkDICOMDatabase);
  532. QSqlQuery query(d->Database);
  533. query.prepare ( "SELECT PatientsUID FROM Studies WHERE StudyInstanceUID= ?" );
  534. query.bindValue ( 0, studyUID);
  535. query.exec();
  536. QString result;
  537. if (query.next())
  538. {
  539. result = query.value(0).toString();
  540. }
  541. return( result );
  542. }
  543. //------------------------------------------------------------------------------
  544. QHash<QString,QString> ctkDICOMDatabase::descriptionsForFile(QString fileName)
  545. {
  546. Q_D(ctkDICOMDatabase);
  547. QString seriesUID(this->seriesForFile(fileName));
  548. QString studyUID(this->studyForSeries(seriesUID));
  549. QString patientID(this->patientForStudy(studyUID));
  550. QSqlQuery query(d->Database);
  551. query.prepare ( "SELECT SeriesDescription FROM Series WHERE SeriesInstanceUID= ?" );
  552. query.bindValue ( 0, seriesUID);
  553. query.exec();
  554. QHash<QString,QString> result;
  555. if (query.next())
  556. {
  557. result["SeriesDescription"] = query.value(0).toString();
  558. }
  559. query.prepare ( "SELECT StudyDescription FROM Studies WHERE StudyInstanceUID= ?" );
  560. query.bindValue ( 0, studyUID);
  561. query.exec();
  562. if (query.next())
  563. {
  564. result["StudyDescription"] = query.value(0).toString();
  565. }
  566. query.prepare ( "SELECT PatientsName FROM Patients WHERE UID= ?" );
  567. query.bindValue ( 0, patientID);
  568. query.exec();
  569. if (query.next())
  570. {
  571. result["PatientsName"] = query.value(0).toString();
  572. }
  573. return( result );
  574. }
  575. //------------------------------------------------------------------------------
  576. QString ctkDICOMDatabase::descriptionForSeries(const QString seriesUID)
  577. {
  578. Q_D(ctkDICOMDatabase);
  579. QString result;
  580. QSqlQuery query(d->Database);
  581. query.prepare ( "SELECT SeriesDescription FROM Series WHERE SeriesInstanceUID= ?" );
  582. query.bindValue ( 0, seriesUID);
  583. query.exec();
  584. if (query.next())
  585. {
  586. result = query.value(0).toString();
  587. }
  588. return result;
  589. }
  590. //------------------------------------------------------------------------------
  591. QString ctkDICOMDatabase::descriptionForStudy(const QString studyUID)
  592. {
  593. Q_D(ctkDICOMDatabase);
  594. QString result;
  595. QSqlQuery query(d->Database);
  596. query.prepare ( "SELECT StudyDescription FROM Studies WHERE StudyInstanceUID= ?" );
  597. query.bindValue ( 0, studyUID);
  598. query.exec();
  599. if (query.next())
  600. {
  601. result = query.value(0).toString();
  602. }
  603. return result;
  604. }
  605. //------------------------------------------------------------------------------
  606. QString ctkDICOMDatabase::nameForPatient(const QString patientUID)
  607. {
  608. Q_D(ctkDICOMDatabase);
  609. QString result;
  610. QSqlQuery query(d->Database);
  611. query.prepare ( "SELECT PatientsName FROM Patients WHERE UID= ?" );
  612. query.bindValue ( 0, patientUID);
  613. query.exec();
  614. if (query.next())
  615. {
  616. result = query.value(0).toString();
  617. }
  618. return result;
  619. }
  620. //------------------------------------------------------------------------------
  621. QStringList ctkDICOMDatabase::seriesForStudy(QString studyUID)
  622. {
  623. Q_D(ctkDICOMDatabase);
  624. QSqlQuery query(d->Database);
  625. query.prepare ( "SELECT SeriesInstanceUID FROM Series WHERE StudyInstanceUID=?");
  626. query.bindValue ( 0, studyUID );
  627. query.exec();
  628. QStringList result;
  629. while (query.next())
  630. {
  631. result << query.value(0).toString();
  632. }
  633. return( result );
  634. }
  635. //------------------------------------------------------------------------------
  636. QStringList ctkDICOMDatabase::instancesForSeries(const QString seriesUID)
  637. {
  638. Q_D(ctkDICOMDatabase);
  639. QSqlQuery query(d->Database);
  640. query.prepare("SELECT SOPInstanceUID FROM Images WHERE SeriesInstanceUID= ?");
  641. query.bindValue(0, seriesUID);
  642. query.exec();
  643. QStringList result;
  644. while (query.next())
  645. {
  646. result << query.value(0).toString();
  647. }
  648. return result;
  649. }
  650. //------------------------------------------------------------------------------
  651. QStringList ctkDICOMDatabase::filesForSeries(QString seriesUID)
  652. {
  653. Q_D(ctkDICOMDatabase);
  654. QSqlQuery query(d->Database);
  655. query.prepare ( "SELECT Filename FROM Images WHERE SeriesInstanceUID=?");
  656. query.bindValue ( 0, seriesUID );
  657. query.exec();
  658. QStringList result;
  659. while (query.next())
  660. {
  661. result << query.value(0).toString();
  662. }
  663. return( result );
  664. }
  665. //------------------------------------------------------------------------------
  666. QString ctkDICOMDatabase::fileForInstance(QString sopInstanceUID)
  667. {
  668. Q_D(ctkDICOMDatabase);
  669. QSqlQuery query(d->Database);
  670. query.prepare ( "SELECT Filename FROM Images WHERE SOPInstanceUID=?");
  671. query.bindValue ( 0, sopInstanceUID );
  672. query.exec();
  673. QString result;
  674. if (query.next())
  675. {
  676. result = query.value(0).toString();
  677. }
  678. return( result );
  679. }
  680. //------------------------------------------------------------------------------
  681. QString ctkDICOMDatabase::seriesForFile(QString fileName)
  682. {
  683. Q_D(ctkDICOMDatabase);
  684. QSqlQuery query(d->Database);
  685. query.prepare ( "SELECT SeriesInstanceUID FROM Images WHERE Filename=?");
  686. query.bindValue ( 0, fileName );
  687. query.exec();
  688. QString result;
  689. if (query.next())
  690. {
  691. result = query.value(0).toString();
  692. }
  693. return( result );
  694. }
  695. //------------------------------------------------------------------------------
  696. QString ctkDICOMDatabase::instanceForFile(QString fileName)
  697. {
  698. Q_D(ctkDICOMDatabase);
  699. QSqlQuery query(d->Database);
  700. query.prepare ( "SELECT SOPInstanceUID FROM Images WHERE Filename=?");
  701. query.bindValue ( 0, fileName );
  702. query.exec();
  703. QString result;
  704. if (query.next())
  705. {
  706. result = query.value(0).toString();
  707. }
  708. return( result );
  709. }
  710. //------------------------------------------------------------------------------
  711. QDateTime ctkDICOMDatabase::insertDateTimeForInstance(QString sopInstanceUID)
  712. {
  713. Q_D(ctkDICOMDatabase);
  714. QSqlQuery query(d->Database);
  715. query.prepare ( "SELECT InsertTimestamp FROM Images WHERE SOPInstanceUID=?");
  716. query.bindValue ( 0, sopInstanceUID );
  717. query.exec();
  718. QDateTime result;
  719. if (query.next())
  720. {
  721. result = QDateTime::fromString(query.value(0).toString(), Qt::ISODate);
  722. }
  723. return( result );
  724. }
  725. //
  726. // instance header methods
  727. //
  728. //------------------------------------------------------------------------------
  729. QStringList ctkDICOMDatabase::allFiles()
  730. {
  731. Q_D(ctkDICOMDatabase);
  732. return d->filenames("Images");
  733. }
  734. //------------------------------------------------------------------------------
  735. void ctkDICOMDatabase::loadInstanceHeader (QString sopInstanceUID)
  736. {
  737. Q_D(ctkDICOMDatabase);
  738. QSqlQuery query(d->Database);
  739. query.prepare ( "SELECT Filename FROM Images WHERE SOPInstanceUID=?");
  740. query.bindValue ( 0, sopInstanceUID );
  741. query.exec();
  742. if (query.next())
  743. {
  744. QString fileName = query.value(0).toString();
  745. this->loadFileHeader(fileName);
  746. }
  747. return;
  748. }
  749. //------------------------------------------------------------------------------
  750. void ctkDICOMDatabase::loadFileHeader (QString fileName)
  751. {
  752. Q_D(ctkDICOMDatabase);
  753. d->LoadedHeader.clear();
  754. DcmFileFormat fileFormat;
  755. OFCondition status = fileFormat.loadFile(fileName.toLatin1().data());
  756. if (status.good())
  757. {
  758. DcmDataset *dataset = fileFormat.getDataset();
  759. DcmStack stack;
  760. while (dataset->nextObject(stack, true) == EC_Normal)
  761. {
  762. DcmObject *dO = stack.top();
  763. if (dO)
  764. {
  765. QString tag = QString("%1,%2").arg(
  766. dO->getGTag(),4,16,QLatin1Char('0')).arg(
  767. dO->getETag(),4,16,QLatin1Char('0'));
  768. std::ostringstream s;
  769. dO->print(s);
  770. d->LoadedHeader[tag] = QString(s.str().c_str());
  771. }
  772. }
  773. }
  774. return;
  775. }
  776. //------------------------------------------------------------------------------
  777. QStringList ctkDICOMDatabase::headerKeys ()
  778. {
  779. Q_D(ctkDICOMDatabase);
  780. return (d->LoadedHeader.keys());
  781. }
  782. //------------------------------------------------------------------------------
  783. QString ctkDICOMDatabase::headerValue (QString key)
  784. {
  785. Q_D(ctkDICOMDatabase);
  786. return (d->LoadedHeader[key]);
  787. }
  788. //
  789. // instanceValue and fileValue methods
  790. //
  791. //------------------------------------------------------------------------------
  792. QString ctkDICOMDatabase::instanceValue(QString sopInstanceUID, QString tag)
  793. {
  794. QString value = this->cachedTag(sopInstanceUID, tag);
  795. if (value == TagNotInInstance || value == ValueIsEmptyString)
  796. {
  797. return "";
  798. }
  799. if (value != "")
  800. {
  801. return value;
  802. }
  803. unsigned short group, element;
  804. this->tagToGroupElement(tag, group, element);
  805. return( this->instanceValue(sopInstanceUID, group, element) );
  806. }
  807. //------------------------------------------------------------------------------
  808. QString ctkDICOMDatabase::instanceValue(const QString sopInstanceUID, const unsigned short group, const unsigned short element)
  809. {
  810. QString tag = this->groupElementToTag(group,element);
  811. QString value = this->cachedTag(sopInstanceUID, tag);
  812. if (value == TagNotInInstance || value == ValueIsEmptyString)
  813. {
  814. return "";
  815. }
  816. if (value != "")
  817. {
  818. return value;
  819. }
  820. QString filePath = this->fileForInstance(sopInstanceUID);
  821. if (filePath != "" )
  822. {
  823. value = this->fileValue(filePath, group, element);
  824. return( value );
  825. }
  826. else
  827. {
  828. return ("");
  829. }
  830. }
  831. //------------------------------------------------------------------------------
  832. QString ctkDICOMDatabase::fileValue(const QString fileName, QString tag)
  833. {
  834. unsigned short group, element;
  835. this->tagToGroupElement(tag, group, element);
  836. QString sopInstanceUID = this->instanceForFile(fileName);
  837. QString value = this->cachedTag(sopInstanceUID, tag);
  838. if (value == TagNotInInstance || value == ValueIsEmptyString)
  839. {
  840. return "";
  841. }
  842. if (value != "")
  843. {
  844. return value;
  845. }
  846. return( this->fileValue(fileName, group, element) );
  847. }
  848. //------------------------------------------------------------------------------
  849. QString ctkDICOMDatabase::fileValue(const QString fileName, const unsigned short group, const unsigned short element)
  850. {
  851. // here is where the real lookup happens
  852. // - first we check the tagCache to see if the value exists for this instance tag
  853. // If not,
  854. // - for now we create a ctkDICOMItem and extract the value from there
  855. // - then we convert to the appropriate type of string
  856. //
  857. //As an optimization we could consider
  858. // - check if we are currently looking at the dataset for this fileName
  859. // - if so, are we looking for a group/element that is past the last one
  860. // accessed
  861. // -- if so, keep looking for the requested group/element
  862. // -- if not, start again from the begining
  863. QString tag = this->groupElementToTag(group, element);
  864. QString sopInstanceUID = this->instanceForFile(fileName);
  865. QString value = this->cachedTag(sopInstanceUID, tag);
  866. if (value == TagNotInInstance || value == ValueIsEmptyString)
  867. {
  868. return "";
  869. }
  870. if (value != "")
  871. {
  872. return value;
  873. }
  874. ctkDICOMItem dataset;
  875. dataset.InitializeFromFile(fileName);
  876. if (!dataset.IsInitialized())
  877. {
  878. logger.error( "File " + fileName + " could not be initialized.");
  879. return "";
  880. }
  881. DcmTagKey tagKey(group, element);
  882. value = dataset.GetAllElementValuesAsString(tagKey);
  883. this->cacheTag(sopInstanceUID, tag, value);
  884. return value;
  885. }
  886. //------------------------------------------------------------------------------
  887. bool ctkDICOMDatabase::tagToGroupElement(const QString tag, unsigned short& group, unsigned short& element)
  888. {
  889. QStringList groupElement = tag.split(",");
  890. bool groupOK, elementOK;
  891. if (groupElement.length() != 2)
  892. {
  893. return false;
  894. }
  895. group = groupElement[0].toUInt(&groupOK, 16);
  896. element = groupElement[1].toUInt(&elementOK, 16);
  897. return( groupOK && elementOK );
  898. }
  899. //------------------------------------------------------------------------------
  900. QString ctkDICOMDatabase::groupElementToTag(const unsigned short& group, const unsigned short& element)
  901. {
  902. return QString("%1,%2").arg(group,4,16,QLatin1Char('0')).arg(element,4,16,QLatin1Char('0'));
  903. }
  904. //
  905. // methods related to insert
  906. //
  907. //------------------------------------------------------------------------------
  908. void ctkDICOMDatabase::prepareInsert()
  909. {
  910. Q_D(ctkDICOMDatabase);
  911. // Although resetLastInsertedValues is called when items are deleted through
  912. // this database connection, there may be concurrent database modifications
  913. // (even in the same application) through other connections.
  914. // Therefore, we cannot be sure that the last added patient, study, series,
  915. // items are still in the database. We clear cached Last... IDs to make sure
  916. // the patient, study, series items are created.
  917. d->resetLastInsertedValues();
  918. }
  919. //------------------------------------------------------------------------------
  920. void ctkDICOMDatabase::insert( DcmItem *item, bool storeFile, bool generateThumbnail)
  921. {
  922. if (!item)
  923. {
  924. return;
  925. }
  926. ctkDICOMItem ctkDataset;
  927. ctkDataset.InitializeFromItem(item, false /* do not take ownership */);
  928. this->insert(ctkDataset,storeFile,generateThumbnail);
  929. }
  930. //------------------------------------------------------------------------------
  931. void ctkDICOMDatabase::insert( const ctkDICOMItem& ctkDataset, bool storeFile, bool generateThumbnail)
  932. {
  933. Q_D(ctkDICOMDatabase);
  934. d->insert(ctkDataset, QString(), storeFile, generateThumbnail);
  935. }
  936. //------------------------------------------------------------------------------
  937. void ctkDICOMDatabase::insert ( const QString& filePath, bool storeFile, bool generateThumbnail, bool createHierarchy, const QString& destinationDirectoryName)
  938. {
  939. Q_D(ctkDICOMDatabase);
  940. Q_UNUSED(createHierarchy);
  941. Q_UNUSED(destinationDirectoryName);
  942. /// first we check if the file is already in the database
  943. if (fileExistsAndUpToDate(filePath))
  944. {
  945. logger.debug( "File " + filePath + " already added.");
  946. return;
  947. }
  948. if (d->LoggedExecVerbose)
  949. {
  950. logger.debug( "Processing " + filePath );
  951. }
  952. ctkDICOMItem ctkDataset;
  953. ctkDataset.InitializeFromFile(filePath);
  954. if ( ctkDataset.IsInitialized() )
  955. {
  956. d->insert( ctkDataset, filePath, storeFile, generateThumbnail );
  957. }
  958. else
  959. {
  960. logger.warn(QString("Could not read DICOM file:") + filePath);
  961. }
  962. }
  963. //------------------------------------------------------------------------------
  964. int ctkDICOMDatabasePrivate::insertPatient(const ctkDICOMItem& ctkDataset)
  965. {
  966. int dbPatientID;
  967. // Check if patient is already present in the db
  968. // TODO: maybe add birthdate check for extra safety
  969. QString patientID(ctkDataset.GetElementAsString(DCM_PatientID) );
  970. QString patientsName(ctkDataset.GetElementAsString(DCM_PatientName) );
  971. QString patientsBirthDate(ctkDataset.GetElementAsString(DCM_PatientBirthDate) );
  972. QSqlQuery checkPatientExistsQuery(Database);
  973. checkPatientExistsQuery.prepare ( "SELECT * FROM Patients WHERE PatientID = ? AND PatientsName = ?" );
  974. checkPatientExistsQuery.bindValue ( 0, patientID );
  975. checkPatientExistsQuery.bindValue ( 1, patientsName );
  976. loggedExec(checkPatientExistsQuery);
  977. if (checkPatientExistsQuery.next())
  978. {
  979. // we found him
  980. dbPatientID = checkPatientExistsQuery.value(checkPatientExistsQuery.record().indexOf("UID")).toInt();
  981. qDebug() << "Found patient in the database as UId: " << dbPatientID;
  982. }
  983. else
  984. {
  985. // Insert it
  986. QString patientsBirthTime(ctkDataset.GetElementAsString(DCM_PatientBirthTime) );
  987. QString patientsSex(ctkDataset.GetElementAsString(DCM_PatientSex) );
  988. QString patientsAge(ctkDataset.GetElementAsString(DCM_PatientAge) );
  989. QString patientComments(ctkDataset.GetElementAsString(DCM_PatientComments) );
  990. QSqlQuery insertPatientStatement ( Database );
  991. insertPatientStatement.prepare ( "INSERT INTO Patients ('UID', 'PatientsName', 'PatientID', 'PatientsBirthDate', 'PatientsBirthTime', 'PatientsSex', 'PatientsAge', 'PatientsComments' ) values ( NULL, ?, ?, ?, ?, ?, ?, ? )" );
  992. insertPatientStatement.bindValue ( 0, patientsName );
  993. insertPatientStatement.bindValue ( 1, patientID );
  994. insertPatientStatement.bindValue ( 2, QDate::fromString ( patientsBirthDate, "yyyyMMdd" ) );
  995. insertPatientStatement.bindValue ( 3, patientsBirthTime );
  996. insertPatientStatement.bindValue ( 4, patientsSex );
  997. // TODO: shift patient's age to study,
  998. // since this is not a patient level attribute in images
  999. // insertPatientStatement.bindValue ( 5, patientsAge );
  1000. insertPatientStatement.bindValue ( 6, patientComments );
  1001. loggedExec(insertPatientStatement);
  1002. dbPatientID = insertPatientStatement.lastInsertId().toInt();
  1003. logger.debug ( "New patient inserted: " + QString().setNum ( dbPatientID ) );
  1004. qDebug() << "New patient inserted as : " << dbPatientID;
  1005. }
  1006. return dbPatientID;
  1007. }
  1008. //------------------------------------------------------------------------------
  1009. void ctkDICOMDatabasePrivate::insertStudy(const ctkDICOMItem& ctkDataset, int dbPatientID)
  1010. {
  1011. QString studyInstanceUID(ctkDataset.GetElementAsString(DCM_StudyInstanceUID) );
  1012. QSqlQuery checkStudyExistsQuery (Database);
  1013. checkStudyExistsQuery.prepare ( "SELECT * FROM Studies WHERE StudyInstanceUID = ?" );
  1014. checkStudyExistsQuery.bindValue ( 0, studyInstanceUID );
  1015. checkStudyExistsQuery.exec();
  1016. if(!checkStudyExistsQuery.next())
  1017. {
  1018. qDebug() << "Need to insert new study: " << studyInstanceUID;
  1019. QString studyID(ctkDataset.GetElementAsString(DCM_StudyID) );
  1020. QString studyDate(ctkDataset.GetElementAsString(DCM_StudyDate) );
  1021. QString studyTime(ctkDataset.GetElementAsString(DCM_StudyTime) );
  1022. QString accessionNumber(ctkDataset.GetElementAsString(DCM_AccessionNumber) );
  1023. QString modalitiesInStudy(ctkDataset.GetElementAsString(DCM_ModalitiesInStudy) );
  1024. QString institutionName(ctkDataset.GetElementAsString(DCM_InstitutionName) );
  1025. QString performingPhysiciansName(ctkDataset.GetElementAsString(DCM_PerformingPhysicianName) );
  1026. QString referringPhysician(ctkDataset.GetElementAsString(DCM_ReferringPhysicianName) );
  1027. QString studyDescription(ctkDataset.GetElementAsString(DCM_StudyDescription) );
  1028. QSqlQuery insertStudyStatement ( Database );
  1029. insertStudyStatement.prepare ( "INSERT INTO Studies ( 'StudyInstanceUID', 'PatientsUID', 'StudyID', 'StudyDate', 'StudyTime', 'AccessionNumber', 'ModalitiesInStudy', 'InstitutionName', 'ReferringPhysician', 'PerformingPhysiciansName', 'StudyDescription' ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )" );
  1030. insertStudyStatement.bindValue ( 0, studyInstanceUID );
  1031. insertStudyStatement.bindValue ( 1, dbPatientID );
  1032. insertStudyStatement.bindValue ( 2, studyID );
  1033. insertStudyStatement.bindValue ( 3, QDate::fromString ( studyDate, "yyyyMMdd" ) );
  1034. insertStudyStatement.bindValue ( 4, studyTime );
  1035. insertStudyStatement.bindValue ( 5, accessionNumber );
  1036. insertStudyStatement.bindValue ( 6, modalitiesInStudy );
  1037. insertStudyStatement.bindValue ( 7, institutionName );
  1038. insertStudyStatement.bindValue ( 8, referringPhysician );
  1039. insertStudyStatement.bindValue ( 9, performingPhysiciansName );
  1040. insertStudyStatement.bindValue ( 10, studyDescription );
  1041. if ( !insertStudyStatement.exec() )
  1042. {
  1043. logger.error ( "Error executing statament: " + insertStudyStatement.lastQuery() + " Error: " + insertStudyStatement.lastError().text() );
  1044. }
  1045. else
  1046. {
  1047. LastStudyInstanceUID = studyInstanceUID;
  1048. }
  1049. }
  1050. else
  1051. {
  1052. qDebug() << "Used existing study: " << studyInstanceUID;
  1053. }
  1054. }
  1055. //------------------------------------------------------------------------------
  1056. void ctkDICOMDatabasePrivate::insertSeries(const ctkDICOMItem& ctkDataset, QString studyInstanceUID)
  1057. {
  1058. QString seriesInstanceUID(ctkDataset.GetElementAsString(DCM_SeriesInstanceUID) );
  1059. QSqlQuery checkSeriesExistsQuery (Database);
  1060. checkSeriesExistsQuery.prepare ( "SELECT * FROM Series WHERE SeriesInstanceUID = ?" );
  1061. checkSeriesExistsQuery.bindValue ( 0, seriesInstanceUID );
  1062. if (this->LoggedExecVerbose)
  1063. {
  1064. logger.warn ( "Statement: " + checkSeriesExistsQuery.lastQuery() );
  1065. }
  1066. checkSeriesExistsQuery.exec();
  1067. if(!checkSeriesExistsQuery.next())
  1068. {
  1069. qDebug() << "Need to insert new series: " << seriesInstanceUID;
  1070. QString seriesDate(ctkDataset.GetElementAsString(DCM_SeriesDate) );
  1071. QString seriesTime(ctkDataset.GetElementAsString(DCM_SeriesTime) );
  1072. QString seriesDescription(ctkDataset.GetElementAsString(DCM_SeriesDescription) );
  1073. QString modality(ctkDataset.GetElementAsString(DCM_Modality) );
  1074. QString bodyPartExamined(ctkDataset.GetElementAsString(DCM_BodyPartExamined) );
  1075. QString frameOfReferenceUID(ctkDataset.GetElementAsString(DCM_FrameOfReferenceUID) );
  1076. QString contrastAgent(ctkDataset.GetElementAsString(DCM_ContrastBolusAgent) );
  1077. QString scanningSequence(ctkDataset.GetElementAsString(DCM_ScanningSequence) );
  1078. long seriesNumber(ctkDataset.GetElementAsInteger(DCM_SeriesNumber) );
  1079. long acquisitionNumber(ctkDataset.GetElementAsInteger(DCM_AcquisitionNumber) );
  1080. long echoNumber(ctkDataset.GetElementAsInteger(DCM_EchoNumbers) );
  1081. long temporalPosition(ctkDataset.GetElementAsInteger(DCM_TemporalPositionIdentifier) );
  1082. QSqlQuery insertSeriesStatement ( Database );
  1083. insertSeriesStatement.prepare ( "INSERT INTO Series ( 'SeriesInstanceUID', 'StudyInstanceUID', 'SeriesNumber', 'SeriesDate', 'SeriesTime', 'SeriesDescription', 'Modality', 'BodyPartExamined', 'FrameOfReferenceUID', 'AcquisitionNumber', 'ContrastAgent', 'ScanningSequence', 'EchoNumber', 'TemporalPosition' ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )" );
  1084. insertSeriesStatement.bindValue ( 0, seriesInstanceUID );
  1085. insertSeriesStatement.bindValue ( 1, studyInstanceUID );
  1086. insertSeriesStatement.bindValue ( 2, static_cast<int>(seriesNumber) );
  1087. insertSeriesStatement.bindValue ( 3, QDate::fromString ( seriesDate, "yyyyMMdd" ) );
  1088. insertSeriesStatement.bindValue ( 4, seriesTime );
  1089. insertSeriesStatement.bindValue ( 5, seriesDescription );
  1090. insertSeriesStatement.bindValue ( 6, modality );
  1091. insertSeriesStatement.bindValue ( 7, bodyPartExamined );
  1092. insertSeriesStatement.bindValue ( 8, frameOfReferenceUID );
  1093. insertSeriesStatement.bindValue ( 9, static_cast<int>(acquisitionNumber) );
  1094. insertSeriesStatement.bindValue ( 10, contrastAgent );
  1095. insertSeriesStatement.bindValue ( 11, scanningSequence );
  1096. insertSeriesStatement.bindValue ( 12, static_cast<int>(echoNumber) );
  1097. insertSeriesStatement.bindValue ( 13, static_cast<int>(temporalPosition) );
  1098. if ( !insertSeriesStatement.exec() )
  1099. {
  1100. logger.error ( "Error executing statament: "
  1101. + insertSeriesStatement.lastQuery()
  1102. + " Error: " + insertSeriesStatement.lastError().text() );
  1103. LastSeriesInstanceUID = "";
  1104. }
  1105. else
  1106. {
  1107. LastSeriesInstanceUID = seriesInstanceUID;
  1108. }
  1109. }
  1110. else
  1111. {
  1112. qDebug() << "Used existing series: " << seriesInstanceUID;
  1113. }
  1114. }
  1115. //------------------------------------------------------------------------------
  1116. void ctkDICOMDatabase::setTagsToPrecache( const QStringList tags)
  1117. {
  1118. Q_D(ctkDICOMDatabase);
  1119. d->TagsToPrecache = tags;
  1120. }
  1121. //------------------------------------------------------------------------------
  1122. const QStringList ctkDICOMDatabase::tagsToPrecache()
  1123. {
  1124. Q_D(ctkDICOMDatabase);
  1125. return d->TagsToPrecache;
  1126. }
  1127. //------------------------------------------------------------------------------
  1128. bool ctkDICOMDatabasePrivate::openTagCacheDatabase()
  1129. {
  1130. // try to open the database if it's not already open
  1131. if ( this->TagCacheDatabase.isOpen() )
  1132. {
  1133. return true;
  1134. }
  1135. this->TagCacheDatabase = QSqlDatabase::addDatabase(
  1136. "QSQLITE", this->Database.connectionName() + "TagCache");
  1137. this->TagCacheDatabase.setDatabaseName(this->TagCacheDatabaseFilename);
  1138. if ( !this->TagCacheDatabase.open() )
  1139. {
  1140. qDebug() << "TagCacheDatabase would not open!\n";
  1141. qDebug() << "TagCacheDatabaseFilename is: " << this->TagCacheDatabaseFilename << "\n";
  1142. return false;
  1143. }
  1144. // Disable synchronous writing to make modifications faster
  1145. QSqlQuery pragmaSyncQuery(this->TagCacheDatabase);
  1146. pragmaSyncQuery.exec("PRAGMA synchronous = OFF");
  1147. pragmaSyncQuery.finish();
  1148. return true;
  1149. }
  1150. //------------------------------------------------------------------------------
  1151. void ctkDICOMDatabasePrivate::precacheTags( const QString sopInstanceUID )
  1152. {
  1153. Q_Q(ctkDICOMDatabase);
  1154. ctkDICOMItem dataset;
  1155. QString fileName = q->fileForInstance(sopInstanceUID);
  1156. dataset.InitializeFromFile(fileName);
  1157. QStringList sopInstanceUIDs, tags, values;
  1158. foreach (const QString &tag, this->TagsToPrecache)
  1159. {
  1160. unsigned short group, element;
  1161. q->tagToGroupElement(tag, group, element);
  1162. DcmTagKey tagKey(group, element);
  1163. QString value = dataset.GetAllElementValuesAsString(tagKey);
  1164. sopInstanceUIDs << sopInstanceUID;
  1165. tags << tag;
  1166. values << value;
  1167. }
  1168. QSqlQuery transaction( this->TagCacheDatabase );
  1169. transaction.prepare( "BEGIN TRANSACTION" );
  1170. transaction.exec();
  1171. q->cacheTags(sopInstanceUIDs, tags, values);
  1172. transaction = QSqlQuery( this->TagCacheDatabase );
  1173. transaction.prepare( "END TRANSACTION" );
  1174. transaction.exec();
  1175. }
  1176. //------------------------------------------------------------------------------
  1177. void ctkDICOMDatabasePrivate::insert( const ctkDICOMItem& ctkDataset, const QString& filePath, bool storeFile, bool generateThumbnail)
  1178. {
  1179. Q_Q(ctkDICOMDatabase);
  1180. // this is the method that all other insert signatures end up calling
  1181. // after they have pre-parsed their arguments
  1182. // Check to see if the file has already been loaded
  1183. // TODO:
  1184. // It could make sense to actually remove the dataset and re-add it. This needs the remove
  1185. // method we still have to write.
  1186. //
  1187. //
  1188. QString sopInstanceUID ( ctkDataset.GetElementAsString(DCM_SOPInstanceUID) );
  1189. QSqlQuery fileExistsQuery ( Database );
  1190. fileExistsQuery.prepare("SELECT InsertTimestamp,Filename FROM Images WHERE SOPInstanceUID == :sopInstanceUID");
  1191. fileExistsQuery.bindValue(":sopInstanceUID",sopInstanceUID);
  1192. {
  1193. bool success = fileExistsQuery.exec();
  1194. if (!success)
  1195. {
  1196. logger.error("SQLITE ERROR: " + fileExistsQuery.lastError().driverText());
  1197. return;
  1198. }
  1199. bool found = fileExistsQuery.next();
  1200. if (this->LoggedExecVerbose)
  1201. {
  1202. qDebug() << "inserting filePath: " << filePath;
  1203. }
  1204. if (!found)
  1205. {
  1206. if (this->LoggedExecVerbose)
  1207. {
  1208. qDebug() << "database filename for " << sopInstanceUID << " is empty - we should insert on top of it";
  1209. }
  1210. }
  1211. else
  1212. {
  1213. QString databaseFilename(fileExistsQuery.value(1).toString());
  1214. QDateTime fileLastModified(QFileInfo(databaseFilename).lastModified());
  1215. QDateTime databaseInsertTimestamp(QDateTime::fromString(fileExistsQuery.value(0).toString(),Qt::ISODate));
  1216. if ( databaseFilename == filePath && fileLastModified < databaseInsertTimestamp )
  1217. {
  1218. logger.debug ( "File " + databaseFilename + " already added" );
  1219. return;
  1220. }
  1221. else
  1222. {
  1223. QSqlQuery deleteFile ( Database );
  1224. deleteFile.prepare("DELETE FROM Images WHERE SOPInstanceUID == :sopInstanceUID");
  1225. deleteFile.bindValue(":sopInstanceUID",sopInstanceUID);
  1226. bool success = deleteFile.exec();
  1227. if (!success)
  1228. {
  1229. logger.error("SQLITE ERROR deleting old image row: " + deleteFile.lastError().driverText());
  1230. return;
  1231. }
  1232. }
  1233. }
  1234. }
  1235. //If the following fields can not be evaluated, cancel evaluation of the DICOM file
  1236. QString patientsName(ctkDataset.GetElementAsString(DCM_PatientName) );
  1237. QString studyInstanceUID(ctkDataset.GetElementAsString(DCM_StudyInstanceUID) );
  1238. QString seriesInstanceUID(ctkDataset.GetElementAsString(DCM_SeriesInstanceUID) );
  1239. QString patientID(ctkDataset.GetElementAsString(DCM_PatientID) );
  1240. if ( patientID.isEmpty() && !studyInstanceUID.isEmpty() )
  1241. { // Use study instance uid as patient id if patient id is empty - can happen on anonymized datasets
  1242. // see: http://www.na-mic.org/Bug/view.php?id=2040
  1243. logger.warn("Patient ID is empty, using studyInstanceUID as patient ID");
  1244. patientID = studyInstanceUID;
  1245. }
  1246. if ( patientsName.isEmpty() && !patientID.isEmpty() )
  1247. { // Use patient id as name if name is empty - can happen on anonymized datasets
  1248. // see: http://www.na-mic.org/Bug/view.php?id=1643
  1249. patientsName = patientID;
  1250. }
  1251. if ( patientsName.isEmpty() || studyInstanceUID.isEmpty() || patientID.isEmpty() )
  1252. {
  1253. logger.error("Dataset is missing necessary information (patient name, study instance UID, or patient ID)!");
  1254. return;
  1255. }
  1256. // store the file if the database is not in memomry
  1257. // TODO: if we are called from insert(file) we
  1258. // have to do something else
  1259. //
  1260. QString filename = filePath;
  1261. if ( storeFile && !q->isInMemory() && !seriesInstanceUID.isEmpty() )
  1262. {
  1263. // QString studySeriesDirectory = studyInstanceUID + "/" + seriesInstanceUID;
  1264. QString destinationDirectoryName = q->databaseDirectory() + "/dicom/";
  1265. QDir destinationDir(destinationDirectoryName);
  1266. filename = destinationDirectoryName +
  1267. studyInstanceUID + "/" +
  1268. seriesInstanceUID + "/" +
  1269. sopInstanceUID;
  1270. destinationDir.mkpath(studyInstanceUID + "/" +
  1271. seriesInstanceUID);
  1272. if(filePath.isEmpty())
  1273. {
  1274. if (this->LoggedExecVerbose)
  1275. {
  1276. logger.debug ( "Saving file: " + filename );
  1277. }
  1278. if ( !ctkDataset.SaveToFile( filename) )
  1279. {
  1280. logger.error ( "Error saving file: " + filename );
  1281. return;
  1282. }
  1283. }
  1284. else
  1285. {
  1286. // we're inserting an existing file
  1287. QFile currentFile( filePath );
  1288. currentFile.copy(filename);
  1289. if (this->LoggedExecVerbose)
  1290. {
  1291. logger.debug("Copy file from: " + filePath + " to: " + filename);
  1292. }
  1293. }
  1294. }
  1295. //The dbPatientID is a unique number within the database,
  1296. //generated by the sqlite autoincrement
  1297. //The patientID is the (non-unique) DICOM patient id
  1298. int dbPatientID = LastPatientUID;
  1299. if ( patientID != "" && patientsName != "" )
  1300. {
  1301. //Speed up: Check if patient is the same as in last file;
  1302. // very probable, as all images belonging to a study have the same patient
  1303. QString patientsBirthDate(ctkDataset.GetElementAsString(DCM_PatientBirthDate) );
  1304. if ( LastPatientID != patientID
  1305. || LastPatientsBirthDate != patientsBirthDate
  1306. || LastPatientsName != patientsName )
  1307. {
  1308. if (this->LoggedExecVerbose)
  1309. {
  1310. qDebug() << "This looks like a different patient from last insert: " << patientID;
  1311. }
  1312. // Ok, something is different from last insert, let's insert him if he's not
  1313. // already in the db.
  1314. dbPatientID = insertPatient( ctkDataset );
  1315. // let users of this class track when things happen
  1316. emit q->patientAdded(dbPatientID, patientID, patientsName, patientsBirthDate);
  1317. /// keep this for the next image
  1318. LastPatientUID = dbPatientID;
  1319. LastPatientID = patientID;
  1320. LastPatientsBirthDate = patientsBirthDate;
  1321. LastPatientsName = patientsName;
  1322. }
  1323. if (this->LoggedExecVerbose)
  1324. {
  1325. qDebug() << "Going to insert this instance with dbPatientID: " << dbPatientID;
  1326. }
  1327. // Patient is in now. Let's continue with the study
  1328. if ( studyInstanceUID != "" && LastStudyInstanceUID != studyInstanceUID )
  1329. {
  1330. insertStudy(ctkDataset,dbPatientID);
  1331. // let users of this class track when things happen
  1332. emit q->studyAdded(studyInstanceUID);
  1333. qDebug() << "Study Added";
  1334. }
  1335. if ( seriesInstanceUID != "" && seriesInstanceUID != LastSeriesInstanceUID )
  1336. {
  1337. insertSeries(ctkDataset, studyInstanceUID);
  1338. // let users of this class track when things happen
  1339. emit q->seriesAdded(seriesInstanceUID);
  1340. qDebug() << "Series Added";
  1341. }
  1342. // TODO: what to do with imported files
  1343. //
  1344. if ( !filename.isEmpty() && !seriesInstanceUID.isEmpty() )
  1345. {
  1346. QSqlQuery checkImageExistsQuery (Database);
  1347. checkImageExistsQuery.prepare ( "SELECT * FROM Images WHERE Filename = ?" );
  1348. checkImageExistsQuery.bindValue ( 0, filename );
  1349. checkImageExistsQuery.exec();
  1350. if (this->LoggedExecVerbose)
  1351. {
  1352. qDebug() << "Maybe add Instance";
  1353. }
  1354. if(!checkImageExistsQuery.next())
  1355. {
  1356. QSqlQuery insertImageStatement ( Database );
  1357. insertImageStatement.prepare ( "INSERT INTO Images ( 'SOPInstanceUID', 'Filename', 'SeriesInstanceUID', 'InsertTimestamp' ) VALUES ( ?, ?, ?, ? )" );
  1358. insertImageStatement.bindValue ( 0, sopInstanceUID );
  1359. insertImageStatement.bindValue ( 1, filename );
  1360. insertImageStatement.bindValue ( 2, seriesInstanceUID );
  1361. insertImageStatement.bindValue ( 3, QDateTime::currentDateTime() );
  1362. insertImageStatement.exec();
  1363. // insert was needed, so cache any application-requested tags
  1364. this->precacheTags(sopInstanceUID);
  1365. // let users of this class track when things happen
  1366. emit q->instanceAdded(sopInstanceUID);
  1367. if (this->LoggedExecVerbose)
  1368. {
  1369. qDebug() << "Instance Added";
  1370. }
  1371. }
  1372. }
  1373. if( generateThumbnail && thumbnailGenerator && !seriesInstanceUID.isEmpty() )
  1374. {
  1375. QString studySeriesDirectory = studyInstanceUID + "/" + seriesInstanceUID;
  1376. //Create thumbnail here
  1377. QString thumbnailPath = q->databaseDirectory() +
  1378. "/thumbs/" + studyInstanceUID + "/" + seriesInstanceUID
  1379. + "/" + sopInstanceUID + ".png";
  1380. QFileInfo thumbnailInfo(thumbnailPath);
  1381. if( !(thumbnailInfo.exists()
  1382. && (thumbnailInfo.lastModified() > QFileInfo(filename).lastModified())))
  1383. {
  1384. QDir(q->databaseDirectory() + "/thumbs/").mkpath(studySeriesDirectory);
  1385. DicomImage dcmImage(QDir::toNativeSeparators(filename).toLatin1());
  1386. thumbnailGenerator->generateThumbnail(&dcmImage, thumbnailPath);
  1387. }
  1388. }
  1389. if (q->isInMemory())
  1390. {
  1391. emit q->databaseChanged();
  1392. }
  1393. }
  1394. else
  1395. {
  1396. qDebug() << "No patient name or no patient id - not inserting!";
  1397. }
  1398. }
  1399. //------------------------------------------------------------------------------
  1400. bool ctkDICOMDatabase::fileExistsAndUpToDate(const QString& filePath)
  1401. {
  1402. Q_D(ctkDICOMDatabase);
  1403. bool result(false);
  1404. QSqlQuery check_filename_query(database());
  1405. check_filename_query.prepare("SELECT InsertTimestamp FROM Images WHERE Filename == ?");
  1406. check_filename_query.bindValue(0,filePath);
  1407. d->loggedExec(check_filename_query);
  1408. if (
  1409. check_filename_query.next() &&
  1410. QFileInfo(filePath).lastModified() < QDateTime::fromString(check_filename_query.value(0).toString(),Qt::ISODate)
  1411. )
  1412. {
  1413. result = true;
  1414. }
  1415. check_filename_query.finish();
  1416. return result;
  1417. }
  1418. //------------------------------------------------------------------------------
  1419. bool ctkDICOMDatabase::isOpen() const
  1420. {
  1421. Q_D(const ctkDICOMDatabase);
  1422. return d->Database.isOpen();
  1423. }
  1424. //------------------------------------------------------------------------------
  1425. bool ctkDICOMDatabase::isInMemory() const
  1426. {
  1427. Q_D(const ctkDICOMDatabase);
  1428. return d->DatabaseFileName == ":memory:";
  1429. }
  1430. //------------------------------------------------------------------------------
  1431. bool ctkDICOMDatabase::removeSeries(const QString& seriesInstanceUID)
  1432. {
  1433. Q_D(ctkDICOMDatabase);
  1434. // get all images from series
  1435. QSqlQuery fileExistsQuery ( d->Database );
  1436. fileExistsQuery.prepare("SELECT Filename, SOPInstanceUID, StudyInstanceUID FROM Images,Series WHERE Series.SeriesInstanceUID = Images.SeriesInstanceUID AND Images.SeriesInstanceUID = :seriesID");
  1437. fileExistsQuery.bindValue(":seriesID",seriesInstanceUID);
  1438. bool success = fileExistsQuery.exec();
  1439. if (!success)
  1440. {
  1441. logger.error("SQLITE ERROR: " + fileExistsQuery.lastError().driverText());
  1442. return false;
  1443. }
  1444. QList< QPair<QString,QString> > removeList;
  1445. while ( fileExistsQuery.next() )
  1446. {
  1447. QString dbFilePath = fileExistsQuery.value(fileExistsQuery.record().indexOf("Filename")).toString();
  1448. QString sopInstanceUID = fileExistsQuery.value(fileExistsQuery.record().indexOf("SOPInstanceUID")).toString();
  1449. QString studyInstanceUID = fileExistsQuery.value(fileExistsQuery.record().indexOf("StudyInstanceUID")).toString();
  1450. QString internalFilePath = studyInstanceUID + "/" + seriesInstanceUID + "/" + sopInstanceUID;
  1451. removeList << qMakePair(dbFilePath,internalFilePath);
  1452. }
  1453. QSqlQuery fileRemove ( d->Database );
  1454. fileRemove.prepare("DELETE FROM Images WHERE SeriesInstanceUID == :seriesID");
  1455. fileRemove.bindValue(":seriesID",seriesInstanceUID);
  1456. logger.debug("SQLITE: removing seriesInstanceUID " + seriesInstanceUID);
  1457. success = fileRemove.exec();
  1458. if (!success)
  1459. {
  1460. logger.error("SQLITE ERROR: could not remove seriesInstanceUID " + seriesInstanceUID);
  1461. logger.error("SQLITE ERROR: " + fileRemove.lastError().driverText());
  1462. }
  1463. QPair<QString,QString> fileToRemove;
  1464. foreach (fileToRemove, removeList)
  1465. {
  1466. QString dbFilePath = fileToRemove.first;
  1467. QString thumbnailToRemove = databaseDirectory() + "/thumbs/" + fileToRemove.second + ".png";
  1468. // check that the file is below our internal storage
  1469. if (dbFilePath.startsWith( databaseDirectory() + "/dicom/"))
  1470. {
  1471. if (!dbFilePath.endsWith(fileToRemove.second))
  1472. {
  1473. logger.error("Database inconsistency detected during delete!");
  1474. continue;
  1475. }
  1476. if (QFile( dbFilePath ).remove())
  1477. {
  1478. if (d->LoggedExecVerbose)
  1479. {
  1480. logger.debug("Removed file " + dbFilePath );
  1481. }
  1482. }
  1483. else
  1484. {
  1485. logger.warn("Failed to remove file " + dbFilePath );
  1486. }
  1487. }
  1488. // Remove thumbnail (if exists)
  1489. QFile thumbnailFile(thumbnailToRemove);
  1490. if (thumbnailFile.exists())
  1491. {
  1492. if (!thumbnailFile.remove())
  1493. {
  1494. logger.warn("Failed to remove thumbnail " + thumbnailToRemove);
  1495. }
  1496. }
  1497. }
  1498. this->cleanup();
  1499. d->resetLastInsertedValues();
  1500. return true;
  1501. }
  1502. //------------------------------------------------------------------------------
  1503. bool ctkDICOMDatabase::cleanup()
  1504. {
  1505. Q_D(ctkDICOMDatabase);
  1506. QSqlQuery seriesCleanup ( d->Database );
  1507. seriesCleanup.exec("DELETE FROM Series WHERE ( SELECT COUNT(*) FROM Images WHERE Images.SeriesInstanceUID = Series.SeriesInstanceUID ) = 0;");
  1508. seriesCleanup.exec("DELETE FROM Studies WHERE ( SELECT COUNT(*) FROM Series WHERE Series.StudyInstanceUID = Studies.StudyInstanceUID ) = 0;");
  1509. seriesCleanup.exec("DELETE FROM Patients WHERE ( SELECT COUNT(*) FROM Studies WHERE Studies.PatientsUID = Patients.UID ) = 0;");
  1510. return true;
  1511. }
  1512. //------------------------------------------------------------------------------
  1513. bool ctkDICOMDatabase::removeStudy(const QString& studyInstanceUID)
  1514. {
  1515. Q_D(ctkDICOMDatabase);
  1516. QSqlQuery seriesForStudy( d->Database );
  1517. seriesForStudy.prepare("SELECT SeriesInstanceUID FROM Series WHERE StudyInstanceUID = :studyID");
  1518. seriesForStudy.bindValue(":studyID", studyInstanceUID);
  1519. bool success = seriesForStudy.exec();
  1520. if (!success)
  1521. {
  1522. logger.error("SQLITE ERROR: " + seriesForStudy.lastError().driverText());
  1523. return false;
  1524. }
  1525. bool result = true;
  1526. while ( seriesForStudy.next() )
  1527. {
  1528. QString seriesInstanceUID = seriesForStudy.value(seriesForStudy.record().indexOf("SeriesInstanceUID")).toString();
  1529. if ( ! this->removeSeries(seriesInstanceUID) )
  1530. {
  1531. result = false;
  1532. }
  1533. }
  1534. d->resetLastInsertedValues();
  1535. return result;
  1536. }
  1537. //------------------------------------------------------------------------------
  1538. bool ctkDICOMDatabase::removePatient(const QString& patientID)
  1539. {
  1540. Q_D(ctkDICOMDatabase);
  1541. QSqlQuery studiesForPatient( d->Database );
  1542. studiesForPatient.prepare("SELECT StudyInstanceUID FROM Studies WHERE PatientsUID = :patientsID");
  1543. studiesForPatient.bindValue(":patientsID", patientID);
  1544. bool success = studiesForPatient.exec();
  1545. if (!success)
  1546. {
  1547. logger.error("SQLITE ERROR: " + studiesForPatient.lastError().driverText());
  1548. return false;
  1549. }
  1550. bool result = true;
  1551. while ( studiesForPatient.next() )
  1552. {
  1553. QString studyInstanceUID = studiesForPatient.value(studiesForPatient.record().indexOf("StudyInstanceUID")).toString();
  1554. if ( ! this->removeStudy(studyInstanceUID) )
  1555. {
  1556. result = false;
  1557. }
  1558. }
  1559. d->resetLastInsertedValues();
  1560. return result;
  1561. }
  1562. ///
  1563. /// Code related to the tagCache
  1564. ///
  1565. //------------------------------------------------------------------------------
  1566. bool ctkDICOMDatabase::tagCacheExists()
  1567. {
  1568. Q_D(ctkDICOMDatabase);
  1569. if (d->TagCacheVerified)
  1570. {
  1571. return true;
  1572. }
  1573. if (!d->openTagCacheDatabase())
  1574. {
  1575. return false;
  1576. }
  1577. if (d->TagCacheDatabase.tables().count() == 0)
  1578. {
  1579. return false;
  1580. }
  1581. // check that the table exists
  1582. QSqlQuery cacheExists( d->TagCacheDatabase );
  1583. cacheExists.prepare("SELECT * FROM TagCache LIMIT 1");
  1584. bool success = d->loggedExec(cacheExists);
  1585. if (success)
  1586. {
  1587. d->TagCacheVerified = true;
  1588. return true;
  1589. }
  1590. qDebug() << "TagCacheDatabase NOT verified based on table check!\n";
  1591. return false;
  1592. }
  1593. //------------------------------------------------------------------------------
  1594. bool ctkDICOMDatabase::initializeTagCache()
  1595. {
  1596. Q_D(ctkDICOMDatabase);
  1597. // First, drop any existing table
  1598. if ( this->tagCacheExists() )
  1599. {
  1600. qDebug() << "TagCacheDatabase drop existing table\n";
  1601. QSqlQuery dropCacheTable( d->TagCacheDatabase );
  1602. dropCacheTable.prepare( "DROP TABLE TagCache" );
  1603. d->loggedExec(dropCacheTable);
  1604. }
  1605. // now create a table
  1606. qDebug() << "TagCacheDatabase adding table\n";
  1607. QSqlQuery createCacheTable( d->TagCacheDatabase );
  1608. createCacheTable.prepare(
  1609. "CREATE TABLE TagCache (SOPInstanceUID, Tag, Value, PRIMARY KEY (SOPInstanceUID, Tag))" );
  1610. bool success = d->loggedExec(createCacheTable);
  1611. if (!success)
  1612. {
  1613. return false;
  1614. }
  1615. d->TagCacheVerified = true;
  1616. return true;
  1617. }
  1618. //------------------------------------------------------------------------------
  1619. QString ctkDICOMDatabase::cachedTag(const QString sopInstanceUID, const QString tag)
  1620. {
  1621. Q_D(ctkDICOMDatabase);
  1622. if ( !this->tagCacheExists() )
  1623. {
  1624. if ( !this->initializeTagCache() )
  1625. {
  1626. return( "" );
  1627. }
  1628. }
  1629. QSqlQuery selectValue( d->TagCacheDatabase );
  1630. selectValue.prepare( "SELECT Value FROM TagCache WHERE SOPInstanceUID = :sopInstanceUID AND Tag = :tag" );
  1631. selectValue.bindValue(":sopInstanceUID",sopInstanceUID);
  1632. selectValue.bindValue(":tag",tag);
  1633. d->loggedExec(selectValue);
  1634. QString result("");
  1635. if (selectValue.next())
  1636. {
  1637. result = selectValue.value(0).toString();
  1638. if (result == QString(""))
  1639. {
  1640. result = ValueIsEmptyString;
  1641. }
  1642. }
  1643. return( result );
  1644. }
  1645. //------------------------------------------------------------------------------
  1646. bool ctkDICOMDatabase::cacheTag(const QString sopInstanceUID, const QString tag, const QString value)
  1647. {
  1648. QStringList sopInstanceUIDs, tags, values;
  1649. sopInstanceUIDs << sopInstanceUID;
  1650. tags << tag;
  1651. values << value;
  1652. return this->cacheTags(sopInstanceUIDs, tags, values);
  1653. }
  1654. //------------------------------------------------------------------------------
  1655. bool ctkDICOMDatabase::cacheTags(const QStringList sopInstanceUIDs, const QStringList tags, QStringList values)
  1656. {
  1657. Q_D(ctkDICOMDatabase);
  1658. if ( !this->tagCacheExists() )
  1659. {
  1660. if ( !this->initializeTagCache() )
  1661. {
  1662. return false;
  1663. }
  1664. }
  1665. // replace empty strings with special flag string
  1666. QStringList::iterator i;
  1667. for (i = values.begin(); i != values.end(); ++i)
  1668. {
  1669. if (*i == "")
  1670. {
  1671. *i = TagNotInInstance;
  1672. }
  1673. }
  1674. QSqlQuery insertTags( d->TagCacheDatabase );
  1675. insertTags.prepare( "INSERT OR REPLACE INTO TagCache VALUES(?,?,?)" );
  1676. insertTags.addBindValue(sopInstanceUIDs);
  1677. insertTags.addBindValue(tags);
  1678. insertTags.addBindValue(values);
  1679. return d->loggedExecBatch(insertTags);
  1680. }