Browse Source

Merge remote branch 'origin/tagCache'

Steve Pieper 13 years ago
parent
commit
ebd83b97d1

+ 58 - 3
Libs/DICOM/Core/Testing/Cpp/ctkDICOMDatabaseTest2.cpp

@@ -67,11 +67,11 @@ int ctkDICOMDatabaseTest2( int argc, char * argv [] )
     {
     std::cerr << "ctkDICOMDatabase::openDatabase() failed: "
               << "database should not be in memory" << std::endl;
-    return EXIT_FAILURE;    
+    return EXIT_FAILURE;
     }
 
   bool res = database.initializeDatabase();
-  
+
   if (!res)
     {
     std::cerr << "ctkDICOMDatabase::initializeDatabase() failed." << std::endl;
@@ -97,6 +97,13 @@ int ctkDICOMDatabaseTest2( int argc, char * argv [] )
     return EXIT_FAILURE;
     }
 
+  if ( database.groupElementToTag(group, element) != tag )
+    {
+    std::cerr << "ctkDICOMDatabase: could not convert a uints to tag string" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+
   //
   // Basic test:
   // - insert the file specified on the command line
@@ -112,7 +119,15 @@ int ctkDICOMDatabaseTest2( int argc, char * argv [] )
     std::cerr << "ctkDICOMDatabase: didn't get back the original file path" << std::endl;
     return EXIT_FAILURE;
     }
-  
+
+  QString foundInstance = database.instanceForFile(dicomFilePath);
+
+  if (foundInstance != instanceUID)
+    {
+    std::cerr << "ctkDICOMDatabase: didn't get back the original instance uid" << std::endl;
+    return EXIT_FAILURE;
+    }
+
 
   QString knownSeriesDescription("3D Cor T1 FAST IR-prepped GRE");
 
@@ -124,6 +139,46 @@ int ctkDICOMDatabaseTest2( int argc, char * argv [] )
     return EXIT_FAILURE;
     }
 
+  //
+  // Test the tag cache
+  //
+
+  if (database.tagCacheExists())
+    {
+    std::cerr << "ctkDICOMDatabase: tag cache should not exist in fresh database" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  if (!database.initializeTagCache())
+    {
+    std::cerr << "ctkDICOMDatabase: could not initialize tag cache" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  if (!database.tagCacheExists())
+    {
+    std::cerr << "ctkDICOMDatabase: tag cache should exist but is not detected" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  if (database.cachedTag(instanceUID, tag) != QString(""))
+    {
+    std::cerr << "ctkDICOMDatabase: tag cache should return empty string for unknown instance tag" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  if (!database.cacheTag(instanceUID, tag, knownSeriesDescription))
+    {
+    std::cerr << "ctkDICOMDatabase: could not insert instance tag" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  if (database.cachedTag(instanceUID, tag) != knownSeriesDescription)
+    {
+    std::cerr << "ctkDICOMDatabase: could not retrieve cached tag" << std::endl;
+    return EXIT_FAILURE;
+    }
+
   database.closeDatabase();
   database.initializeDatabase();
 

+ 163 - 2
Libs/DICOM/Core/ctkDICOMDatabase.cpp

@@ -80,6 +80,7 @@ public:
   ///
   bool loggedExec(QSqlQuery& query);
   bool loggedExec(QSqlQuery& query, const QString& queryString);
+  bool LoggedExecVerbose;
 
   // dataset must be set always
   // filePath has to be set if this is an import of an actual file
@@ -106,6 +107,9 @@ public:
   /// parallel inserts are not allowed (yet)
   QMutex insertMutex;
 
+  /// tagCache table has been checked to exist
+  bool TagCacheVerified;
+
   int insertPatient(const ctkDICOMDataset& ctkDataset);
   void insertStudy(const ctkDICOMDataset& ctkDataset, int dbPatientID);
   void insertSeries( const ctkDICOMDataset& ctkDataset, QString studyInstanceUID);
@@ -118,7 +122,9 @@ public:
 ctkDICOMDatabasePrivate::ctkDICOMDatabasePrivate(ctkDICOMDatabase& o): q_ptr(&o)
 {
   this->thumbnailGenerator = NULL;
+  this->LoggedExecVerbose = false;
   this->LastPatientUID = -1;
+  this->TagCacheVerified = false;
 }
 
 //------------------------------------------------------------------------------
@@ -175,7 +181,10 @@ bool ctkDICOMDatabasePrivate::loggedExec(QSqlQuery& query, const QString& queryS
     }
   else
     {
+      if (LoggedExecVerbose)
+      {
       logger.debug( "SQL worked!\n SQL: " + query.lastQuery());
+      }
     }
   return (success);
 }
@@ -326,6 +335,10 @@ void ctkDICOMDatabase::closeDatabase()
   d->Database.close();
 }
 
+//
+// Patient/study/series convenience methods
+//
+
 //------------------------------------------------------------------------------
 QStringList ctkDICOMDatabase::patients()
 {
@@ -398,13 +411,33 @@ QString ctkDICOMDatabase::fileForInstance(QString sopInstanceUID)
   query.bindValue ( 0, sopInstanceUID );
   query.exec();
   QString result;
-  if (query.next()) 
+  if (query.next())
+    {
+    result = query.value(0).toString();
+    }
+  return( result );
+}
+
+//------------------------------------------------------------------------------
+QString ctkDICOMDatabase::instanceForFile(QString fileName)
+{
+  Q_D(ctkDICOMDatabase);
+  QSqlQuery query(d->Database);
+  query.prepare ( "SELECT SOPInstanceUID FROM Images WHERE Filename=?");
+  query.bindValue ( 0, fileName );
+  query.exec();
+  QString result;
+  if (query.next())
     {
     result = query.value(0).toString();
     }
   return( result );
 }
 
+//
+// instance header methods
+//
+
 //------------------------------------------------------------------------------
 void ctkDICOMDatabase::loadInstanceHeader (QString sopInstanceUID)
 {
@@ -460,9 +493,18 @@ QString ctkDICOMDatabase::headerValue (QString key)
   return (d->LoadedHeader[key]);
 }
 
+//
+// instanceValue and fileValue methods
+//
+
 //------------------------------------------------------------------------------
 QString ctkDICOMDatabase::instanceValue(QString sopInstanceUID, QString tag)
 {
+  QString value = this->cachedTag(sopInstanceUID, tag);
+  if (value != "")
+    {
+    return value;
+    }
   unsigned short group, element;
   this->tagToGroupElement(tag, group, element);
   return( this->instanceValue(sopInstanceUID, group, element) );
@@ -471,6 +513,12 @@ QString ctkDICOMDatabase::instanceValue(QString sopInstanceUID, QString tag)
 //------------------------------------------------------------------------------
 QString ctkDICOMDatabase::instanceValue(const QString sopInstanceUID, const unsigned short group, const unsigned short element)
 {
+  QString tag = this->groupElementToTag(group,element);
+  QString value = this->cachedTag(sopInstanceUID, tag);
+  if (value != "")
+    {
+    return value;
+    }
   QString filePath = this->fileForInstance(sopInstanceUID);
   if (filePath != "" )
     {
@@ -488,6 +536,12 @@ QString ctkDICOMDatabase::fileValue(const QString fileName, QString tag)
 {
   unsigned short group, element;
   this->tagToGroupElement(tag, group, element);
+  QString sopInstanceUID = this->instanceForFile(fileName);
+  QString value = this->cachedTag(sopInstanceUID, tag);
+  if (value != "")
+    {
+    return value;
+    }
   return( this->fileValue(fileName, group, element) );
 }
 
@@ -495,6 +549,8 @@ QString ctkDICOMDatabase::fileValue(const QString fileName, QString tag)
 QString ctkDICOMDatabase::fileValue(const QString fileName, const unsigned short group, const unsigned short element)
 {
   // here is where the real lookup happens
+  // - first we check the tagCache to see if the value exists for this instance tag
+  // If not,
   // - for now we create a ctkDICOMDataset and extract the value from there
   // - then we convert to the appropriate type of string
   //
@@ -505,9 +561,17 @@ QString ctkDICOMDatabase::fileValue(const QString fileName, const unsigned short
   //   -- if so, keep looking for the requested group/element
   //   -- if not, start again from the begining
 
+  QString tag = this->groupElementToTag(group, element);
+  QString sopInstanceUID = this->instanceForFile(fileName);
+  QString value = this->cachedTag(sopInstanceUID, tag);
+  if (value != "")
+    {
+    return value;
+    }
+
   ctkDICOMDataset dataset;
   dataset.InitializeFromFile(fileName);
-  
+
   DcmTagKey tagKey(group, element);
 
   return( dataset.GetAllElementValuesAsString(tagKey) );
@@ -525,6 +589,16 @@ bool ctkDICOMDatabase::tagToGroupElement(const QString tag, unsigned short& grou
 }
 
 //------------------------------------------------------------------------------
+QString ctkDICOMDatabase::groupElementToTag(const unsigned short& group, const unsigned short& element)
+{
+  return QString("%1,%2").arg(group,4,16,QLatin1Char('0')).arg(element,4,16,QLatin1Char('0'));
+}
+
+//
+// methods related to insert
+//
+
+//------------------------------------------------------------------------------
 void ctkDICOMDatabase::insert( DcmDataset *dataset, bool storeFile, bool generateThumbnail)
 {
   if (!dataset)
@@ -625,6 +699,7 @@ int ctkDICOMDatabasePrivate::insertPatient(const ctkDICOMDataset& ctkDataset)
     return dbPatientID;
 }
 
+//------------------------------------------------------------------------------
 void ctkDICOMDatabasePrivate::insertStudy(const ctkDICOMDataset& ctkDataset, int dbPatientID)
 {
   QString studyInstanceUID(ctkDataset.GetElementAsString(DCM_StudyInstanceUID) );
@@ -670,6 +745,7 @@ void ctkDICOMDatabasePrivate::insertStudy(const ctkDICOMDataset& ctkDataset, int
     }
 }
 
+//------------------------------------------------------------------------------
 void ctkDICOMDatabasePrivate::insertSeries(const ctkDICOMDataset& ctkDataset, QString studyInstanceUID)
 {
   QString seriesInstanceUID(ctkDataset.GetElementAsString(DCM_SeriesInstanceUID) );
@@ -721,6 +797,7 @@ void ctkDICOMDatabasePrivate::insertSeries(const ctkDICOMDataset& ctkDataset, QS
     }
 }
 
+//------------------------------------------------------------------------------
 void ctkDICOMDatabasePrivate::insert( const ctkDICOMDataset& ctkDataset, const QString& filePath, bool storeFile, bool generateThumbnail)
 {
   Q_Q(ctkDICOMDatabase);
@@ -912,6 +989,7 @@ void ctkDICOMDatabasePrivate::insert( const ctkDICOMDataset& ctkDataset, const Q
     }
 }
 
+//------------------------------------------------------------------------------
 bool ctkDICOMDatabase::fileExistsAndUpToDate(const QString& filePath)
 {
   Q_D(ctkDICOMDatabase);
@@ -933,18 +1011,21 @@ bool ctkDICOMDatabase::fileExistsAndUpToDate(const QString& filePath)
 }
 
 
+//------------------------------------------------------------------------------
 bool ctkDICOMDatabase::isOpen() const
 {
   Q_D(const ctkDICOMDatabase);
   return d->Database.isOpen();
 }
 
+//------------------------------------------------------------------------------
 bool ctkDICOMDatabase::isInMemory() const
 {
   Q_D(const ctkDICOMDatabase);
   return d->DatabaseFileName == ":memory:";
 }
 
+//------------------------------------------------------------------------------
 bool ctkDICOMDatabase::removeSeries(const QString& seriesInstanceUID)
 {
   Q_D(ctkDICOMDatabase);
@@ -1021,6 +1102,7 @@ bool ctkDICOMDatabase::removeSeries(const QString& seriesInstanceUID)
   return true;
 }
 
+//------------------------------------------------------------------------------
 bool ctkDICOMDatabase::cleanup()
 {
   Q_D(ctkDICOMDatabase);
@@ -1031,6 +1113,7 @@ bool ctkDICOMDatabase::cleanup()
   return true;
 }
 
+//------------------------------------------------------------------------------
 bool ctkDICOMDatabase::removeStudy(const QString& studyInstanceUID)
 {
   Q_D(ctkDICOMDatabase);
@@ -1057,6 +1140,7 @@ bool ctkDICOMDatabase::removeStudy(const QString& studyInstanceUID)
   return result;
 }
 
+//------------------------------------------------------------------------------
 bool ctkDICOMDatabase::removePatient(const QString& patientID)
 {
   Q_D(ctkDICOMDatabase);
@@ -1085,3 +1169,80 @@ bool ctkDICOMDatabase::removePatient(const QString& patientID)
   d->LastPatientUID = -1;
   return result;
 }
+
+///
+/// Code related to the tagCache
+///
+
+//------------------------------------------------------------------------------
+bool ctkDICOMDatabase::tagCacheExists()
+{
+  Q_D(ctkDICOMDatabase);
+  if (d->TagCacheVerified)
+    {
+    return true;
+    }
+  QSqlQuery cacheExists( d->Database );
+  cacheExists.prepare("SELECT * FROM TagCache LIMIT 1");
+  bool success = d->loggedExec(cacheExists);
+  if (success)
+    {
+    d->TagCacheVerified = true;
+    }
+  return false;
+}
+
+//------------------------------------------------------------------------------
+bool ctkDICOMDatabase::initializeTagCache()
+{
+  Q_D(ctkDICOMDatabase);
+
+  // First, drop any existing table
+  if ( this->tagCacheExists() )
+    {
+    QSqlQuery dropCacheTable( d->Database );
+    dropCacheTable.prepare( "DROP TABLE TagCache" );
+    d->loggedExec(dropCacheTable);
+    }
+
+  // now create a table
+  QSqlQuery createCacheTable( d->Database );
+  createCacheTable.prepare(
+    "CREATE TABLE TagCache (SOPInstanceUID, Tag, Value, PRIMARY KEY (SOPInstanceUID, Tag))" );
+  bool success = d->loggedExec(createCacheTable);
+  if (success)
+    {
+    d->TagCacheVerified = true;
+    return true;
+    }
+  return false;
+}
+
+//------------------------------------------------------------------------------
+QString ctkDICOMDatabase::cachedTag(const QString sopInstanceUID, const QString tag)
+{
+  Q_D(ctkDICOMDatabase);
+  QSqlQuery selectValue( d->Database );
+  selectValue.prepare( "SELECT Value FROM TagCache WHERE SOPInstanceUID = :sopInstanceUID AND Tag = :tag" );
+  selectValue.bindValue(":sopInstanceUID",sopInstanceUID);
+  selectValue.bindValue(":tag",tag);
+  d->loggedExec(selectValue);
+  QString result("");
+  if (selectValue.next())
+    {
+    result = selectValue.value(0).toString();
+    }
+  return( result );
+}
+
+//------------------------------------------------------------------------------
+bool ctkDICOMDatabase::cacheTag(const QString sopInstanceUID, const QString tag, const QString value)
+{
+  Q_D(ctkDICOMDatabase);
+  QSqlQuery insertTag( d->Database );
+  insertTag.prepare( "INSERT OR REPLACE INTO TagCache VALUES(:sopInstanceUID, :tag, :value)" );
+  insertTag.bindValue(":sopInstanceUID",sopInstanceUID);
+  insertTag.bindValue(":tag",tag);
+  insertTag.bindValue(":value",value);
+  return d->loggedExec(insertTag);
+}

+ 23 - 2
Libs/DICOM/Core/ctkDICOMDatabase.h

@@ -117,6 +117,7 @@ public:
   Q_INVOKABLE QStringList seriesForStudy (const QString studyUID);
   Q_INVOKABLE QStringList filesForSeries (const QString seriesUID);
   Q_INVOKABLE QString fileForInstance (const QString sopInstanceUID);
+  Q_INVOKABLE QString instanceForFile (const QString fileName);
 
   ///
   /// \brief load the header from a file and allow access to elements
@@ -166,12 +167,32 @@ public:
   /// @param key A group,element tag in zero-filled hex
   /// @param group The group portion of the tag as an integer
   /// @param element The element portion of the tag as an integer
-  /// @Returns empty string is element is missing
+  /// @Returns empty string if element is missing
   Q_INVOKABLE QString instanceValue (const QString sopInstanceUID, const QString tag);
   Q_INVOKABLE QString instanceValue (const QString sopInstanceUID, const unsigned short group, const unsigned short element);
   Q_INVOKABLE QString fileValue (const QString fileName, const QString tag);
   Q_INVOKABLE QString fileValue (const QString fileName, const unsigned short group, const unsigned short element);
-  bool tagToGroupElement (const QString tag, unsigned short& group, unsigned short& element);
+  Q_INVOKABLE bool tagToGroupElement (const QString tag, unsigned short& group, unsigned short& element);
+  Q_INVOKABLE QString groupElementToTag (const unsigned short& group, const unsigned short& element);
+
+  ///
+  /// \brief store values of previously requested instance elements
+  /// These are meant to be internal methods used by the instanceValue and fileValue
+  /// methods, but they can be used by calling classes to populate or access
+  /// instance tag values as needed.
+  /// @param sopInstanceUID A string with the uid for a given instance
+  ///                       (corresponding file will be found via database)
+  /// @param key A group,element tag in zero-filled hex
+  /// @Returns empty string if element for uid is missing from cache
+  ///
+  /// Lightweight check of tag cache existence (once db check per runtime)
+  Q_INVOKABLE bool tagCacheExists ();
+  /// Create a tagCache in the current database.  Delete the existing one if it exists.
+  Q_INVOKABLE bool initializeTagCache ();
+  /// Return the value of a cached tag
+  Q_INVOKABLE QString cachedTag (const QString sopInstanceUID, const QString tag);
+  /// Insert an instance tag's value into to the cache
+  Q_INVOKABLE bool cacheTag (const QString sopInstanceUID, const QString tag, const QString value);
 
 
 Q_SIGNALS: