瀏覽代碼

Add new accessors for dicom header values

These should be considerably faster than the loadHeader (and related)
calls since they will only parse and return the requested tags.
Steve Pieper 13 年之前
父節點
當前提交
79f2d70dd3

+ 2 - 0
Libs/DICOM/Core/Testing/Cpp/CMakeLists.txt

@@ -3,6 +3,7 @@ set(KIT ${PROJECT_NAME})
 create_test_sourcelist(Tests ${KIT}CppTests.cpp
   ctkDICOMCoreTest1.cpp
   ctkDICOMDatabaseTest1.cpp
+  ctkDICOMDatabaseTest2.cpp
   ctkDICOMDatasetTest1.cpp
   ctkDICOMIndexerTest1.cpp
   ctkDICOMModelTest1.cpp
@@ -29,6 +30,7 @@ target_link_libraries(${KIT}CppTests ${LIBRARY_NAME})
 
 # ctkDICOMDatabase
 SIMPLE_TEST(ctkDICOMDatabaseTest1)
+SIMPLE_TEST(ctkDICOMDatabaseTest2 ${CTKData_DIR}/Data/DICOM/MRHEAD/000055.IMA)
 SIMPLE_TEST(ctkDICOMDatasetTest1)
 SIMPLE_TEST(ctkDICOMIndexerTest1 )
 

+ 131 - 0
Libs/DICOM/Core/Testing/Cpp/ctkDICOMDatabaseTest2.cpp

@@ -0,0 +1,131 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+// Qt includes
+#include <QCoreApplication>
+#include <QDir>
+#include <QTimer>
+
+// ctkDICOMCore includes
+#include "ctkDICOMDatabase.h"
+
+// STD includes
+#include <iostream>
+#include <cstdlib>
+
+
+int ctkDICOMDatabaseTest2( int argc, char * argv [] )
+{
+  QCoreApplication app(argc, argv);
+
+  if (argc < 2)
+    {
+    std::cerr << "ctkDICOMDatabaseTest2: missing dicom filePath argument";
+    std::cerr << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  QString dicomFilePath(argv[1]);
+
+  ctkDICOMDatabase database;
+  QDir databaseDirectory = QDir::temp();
+  QFileInfo databaseFile(databaseDirectory, QString("database.test"));
+  database.openDatabase(databaseFile.absoluteFilePath());
+
+  if (!database.lastError().isEmpty())
+    {
+    std::cerr << "ctkDICOMDatabase::openDatabase() failed: "
+              << qPrintable(database.lastError()) << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  if (!database.database().isValid())
+    {
+    std::cerr << "ctkDICOMDatabase::openDatabase() failed: "
+              << "invalid sql database" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  if (database.isInMemory())
+    {
+    std::cerr << "ctkDICOMDatabase::openDatabase() failed: "
+              << "database should not be in memory" << std::endl;
+    return EXIT_FAILURE;    
+    }
+
+  bool res = database.initializeDatabase();
+  
+  if (!res)
+    {
+    std::cerr << "ctkDICOMDatabase::initializeDatabase() failed." << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  //
+  // Test that the tag interface works to parse ascii
+  //
+  QString tag("0008,103e");
+  unsigned short group, element;
+  if ( !database.tagToGroupElement(tag, group, element) )
+    {
+    std::cerr << "ctkDICOMDatabase: could not parse tag" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  if ( group != 0x8 || element != 0x103e )
+    {
+    std::cerr << "ctkDICOMDatabase: expected: " << "0008,103e" << std::endl;
+    std::cerr << "ctkDICOMDatabase: got: " << group << " " << element << std::endl;
+    std::cerr << "ctkDICOMDatabase: parsed tag does not match group/element" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  //
+  // Basic test:
+  // - insert the file specified on the command line
+  // - ask for tag values and compare to known results
+  //
+  database.insert(dicomFilePath, false, false);
+  QString instanceUID("1.2.840.113619.2.135.3596.6358736.4843.1115808177.83");
+
+  QString foundFile = database.fileForInstance(instanceUID);
+
+  if (foundFile != dicomFilePath)
+    {
+    std::cerr << "ctkDICOMDatabase: didn't get back the original file path" << std::endl;
+    return EXIT_FAILURE;
+    }
+  
+
+  QString knownSeriesDescription("3D Cor T1 FAST IR-prepped GRE");
+
+  QString foundSeriesDescription = database.instanceValue(instanceUID, tag);
+
+  if (foundSeriesDescription != knownSeriesDescription)
+    {
+    std::cerr << "ctkDICOMDatabase: invalid element value returned" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  database.closeDatabase();
+  database.initializeDatabase();
+
+  return EXIT_SUCCESS;
+}

+ 68 - 5
Libs/DICOM/Core/ctkDICOMDatabase.cpp

@@ -382,6 +382,22 @@ QStringList ctkDICOMDatabase::filesForSeries(QString seriesUID)
 }
 
 //------------------------------------------------------------------------------
+QString ctkDICOMDatabase::fileForInstance(QString sopInstanceUID)
+{
+  Q_D(ctkDICOMDatabase);
+  QSqlQuery query(d->Database);
+  query.prepare ( "SELECT Filename FROM Images WHERE SOPInstanceUID=?");
+  query.bindValue ( 0, sopInstanceUID );
+  query.exec();
+  QString result;
+  if (query.next()) 
+    {
+    result = query.value(0).toString();
+    }
+  return( result );
+}
+
+//------------------------------------------------------------------------------
 void ctkDICOMDatabase::loadInstanceHeader (QString sopInstanceUID)
 {
   Q_D(ctkDICOMDatabase);
@@ -437,11 +453,59 @@ QString ctkDICOMDatabase::headerValue (QString key)
 }
 
 //------------------------------------------------------------------------------
-/*
-void ctkDICOMDatabase::insert ( DcmDataset *dataset ) {
-  this->insert ( dataset, QString() );
+QString ctkDICOMDatabase::instanceValue(QString sopInstanceUID, QString tag)
+{
+  unsigned short group, element;
+  this->tagToGroupElement(tag, group, element);
+  return( this->instanceValue(sopInstanceUID, group, element) );
+}
+
+//------------------------------------------------------------------------------
+QString ctkDICOMDatabase::instanceValue(const QString sopInstanceUID, const unsigned short group, const unsigned short element)
+{
+  QString filePath = this->fileForInstance(sopInstanceUID);
+  if (filePath != "" )
+    {
+    return( this->fileValue(filePath, group, element) );
+    }
+  else
+    {
+    return ("");
+    }
+}
+
+
+//------------------------------------------------------------------------------
+QString ctkDICOMDatabase::fileValue(const QString fileName, QString tag)
+{
+  unsigned short group, element;
+  this->tagToGroupElement(tag, group, element);
+  return( this->fileValue(fileName, group, element) );
+}
+
+//------------------------------------------------------------------------------
+QString ctkDICOMDatabase::fileValue(const QString fileName, const unsigned short group, const unsigned short element)
+{
+  // here is where the real lookup happens
+  // - check if we are currently looking at the dataset for this fileName
+  // - if so, are we looking for a group/element that is past the last one
+  //   accessed
+  //   -- if so, keep looking for the 
+
+  return( "" );
+
+}
+
+//------------------------------------------------------------------------------
+bool ctkDICOMDatabase::tagToGroupElement(const QString tag, unsigned short& group, unsigned short& element)
+{
+  QStringList groupElement = tag.split(",");
+  bool groupOK, elementOK;
+  group = groupElement[0].toUInt(&groupOK, 16);
+  element = groupElement[1].toUInt(&elementOK, 16);
+
+  return( groupOK && elementOK );
 }
-*/
 
 //------------------------------------------------------------------------------
 void ctkDICOMDatabase::insert( DcmDataset *dataset, bool storeFile, bool generateThumbnail)
@@ -970,4 +1034,3 @@ bool ctkDICOMDatabase::removePatient(const QString& patientID)
   d->lastPatientUID = -1;
   return result;
 }
-

+ 28 - 7
Libs/DICOM/Core/ctkDICOMDatabase.h

@@ -113,16 +113,21 @@ public:
   ///
   /// \brief database accessors
   Q_INVOKABLE QStringList patients ();
-  Q_INVOKABLE QStringList studiesForPatient (QString patientUID);
-  Q_INVOKABLE QStringList seriesForStudy (QString studyUID);
-  Q_INVOKABLE QStringList filesForSeries (QString seriesUID);
+  Q_INVOKABLE QStringList studiesForPatient (const QString patientUID);
+  Q_INVOKABLE QStringList seriesForStudy (const QString studyUID);
+  Q_INVOKABLE QStringList filesForSeries (const QString seriesUID);
+  Q_INVOKABLE QString fileForInstance (const QString sopInstanceUID);
 
   ///
   /// \brief load the header from a file and allow access to elements
-  Q_INVOKABLE void loadInstanceHeader (QString sopInstanceUID);
-  Q_INVOKABLE void loadFileHeader (QString fileName);
+  /// @param sopInstanceUID A string with the uid for a given instance
+  ///                       (corresponding file will be found via database)
+  /// @param fileName Full path to a dicom file to load.
+  /// @param key A group,element tag in zero-filled hex
+  Q_INVOKABLE void loadInstanceHeader (const QString sopInstanceUID);
+  Q_INVOKABLE void loadFileHeader (const QString fileName);
   Q_INVOKABLE QStringList headerKeys ();
-  Q_INVOKABLE QString headerValue (QString key);
+  Q_INVOKABLE QString headerValue (const QString key);
 
   /// Insert into the database if not already exsting.
   /// @param dataset The dataset to store into the database. Usually, this is
@@ -139,7 +144,7 @@ public:
   ///                  does only make sense if a full object is received.
   /// @param @generateThumbnail If true, a thumbnail is generated.
   ///
-  void insert( const ctkDICOMDataset& ctkDataset, bool storeFile, bool generateThumbnail);
+  Q_INVOKABLE void insert( const ctkDICOMDataset& ctkDataset, bool storeFile, bool generateThumbnail);
   void insert ( DcmDataset *dataset, bool storeFile = true, bool generateThumbnail = true);
   Q_INVOKABLE void insert ( const QString& filePath, bool storeFile = true, bool generateThumbnail = true, bool createHierarchy = true, const QString& destinationDirectoryName = QString() );
   
@@ -153,6 +158,22 @@ public:
   Q_INVOKABLE bool removePatient(const QString& patientID);
   bool cleanup();
 
+  ///
+  /// \brief access element values for given instance
+  /// @param sopInstanceUID A string with the uid for a given instance
+  ///                       (corresponding file will be found via database)
+  /// @param fileName Full path to a dicom file to load.
+  /// @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
+  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_SIGNALS:
   void databaseChanged();