Przeglądaj źródła

Basic DICOM C-FIND implemented.

Added IndexerBase class to move functionality of ctkDICOMIndexer into superclass.  This allows multiple classes to index DICOM files and/or results from C-FIND requests.  Starting to add functionality into ctkDICOMQuery class and application.
Daniel Blezek 15 lat temu
rodzic
commit
a8d4489710

+ 11 - 10
Applications/ctkDICOMQuery/ctkDICOMQuery.cpp

@@ -35,7 +35,7 @@
 void print_usage()
 {
   std::cerr << "Usage:\n";
-  std::cerr << "  ctkDICOMQuery callingAETitle calledAETitle host port\n";
+  std::cerr << "  ctkDICOMQuery database callingAETitle calledAETitle host port\n";
   return;
 }
 
@@ -54,7 +54,7 @@ int main(int argc, char** argv)
     std::cout << "Debugging" << std::endl;
     }
 
-  if (argc < 4)
+  if (argc < 5)
   {
     print_usage();
     return EXIT_FAILURE;
@@ -63,17 +63,19 @@ int main(int argc, char** argv)
   QApplication app(argc, argv);
   QTextStream out(stdout);
 
-  ctkDICOMQuery query;
+  ctkDICOM myCTK;
+  myCTK.openDatabase ( argv[1] );
 
-  query.setCallingAETitle ( QString ( argv[1] ) );
-  query.setCalledAETitle ( QString ( argv[2] ) );
-  query.setHost ( QString ( argv[3] ) );
+  ctkDICOMQuery query;
+  query.setCallingAETitle ( QString ( argv[2] ) );
+  query.setCalledAETitle ( QString ( argv[3] ) );
+  query.setHost ( QString ( argv[4] ) );
   int port;
   bool ok;
-  port = QString ( argv[4] ).toInt ( &ok );
+  port = QString ( argv[5] ).toInt ( &ok );
   if ( !ok )
     {
-    std::cerr << "Could not convert " << argv[4] << " to an integer" << std::endl;
+    std::cerr << "Could not convert " << argv[5] << " to an integer" << std::endl;
     print_usage();
     return EXIT_FAILURE;
     }
@@ -81,8 +83,7 @@ int main(int argc, char** argv)
 
   try
     {
-    QSqlDatabase db;
-    query.query ( db );
+    query.query ( myCTK.database() );
     }
   catch (std::exception e)
   {

+ 3 - 0
Libs/DICOM/Core/CMakeLists.txt

@@ -26,6 +26,8 @@ SET(KIT_SRCS
   ctkDICOM.h
   ctkDICOMIndexer.cpp
   ctkDICOMIndexer.h
+  ctkDICOMIndexerBase.cpp
+  ctkDICOMIndexerBase.h
   ctkDICOMQuery.cpp
   ctkDICOMQuery.h
   ctkDICOMModel.cpp
@@ -37,6 +39,7 @@ SET(KIT_MOC_SRCS
   ctkDICOM.h
   ctkDICOMModel.h
   ctkDICOMQuery.h
+  ctkDICOMIndexerBase.h
   )
 
 # UI files

+ 6 - 6
Libs/DICOM/Core/Resources/dicom-schema.sql

@@ -14,7 +14,7 @@ DROP TABLE IF EXISTS 'Directories' ;
 
 CREATE TABLE 'Images' (
   'Filename' VARCHAR(1024) NOT NULL ,
-  'SeriesInstanceUID' VARCHAR(255) NOT NULL ,
+  'SeriesInstanceUID' VARCHAR(64) NOT NULL ,
   'InsertTimestamp' VARCHAR(20) NOT NULL ,
   PRIMARY KEY ('Filename') );
 CREATE TABLE 'Patients' (
@@ -27,14 +27,14 @@ CREATE TABLE 'Patients' (
   'PatientsAge' varchar(10) NULL ,
   'PatientsComments' VARCHAR(255) NULL );
 CREATE TABLE 'Series' (
-  'SeriesInstanceUID' VARCHAR(255) NOT NULL ,
-  'StudyInstanceUID' VARCHAR(45) NOT NULL ,
+  'SeriesInstanceUID' VARCHAR(64) NOT NULL ,
+  'StudyInstanceUID' VARCHAR(64) NOT NULL ,
   'SeriesNumber' INT NULL ,
   'SeriesDate' DATE NULL ,
   'SeriesTime' VARCHAR(20) NULL ,
   'SeriesDescription' VARCHAR(255) NULL ,
   'BodyPartExamined' VARCHAR(255) NULL ,
-  'FrameOfReferenceUID' VARCHAR(255) NULL ,
+  'FrameOfReferenceUID' VARCHAR(64) NULL ,
   'AcquisitionNumber' INT NULL ,
   'ContrastAgent' VARCHAR(255) NULL ,
   'ScanningSequence' VARCHAR(45) NULL ,
@@ -42,7 +42,7 @@ CREATE TABLE 'Series' (
   'TemporalPosition' INT NULL ,
   PRIMARY KEY ('SeriesInstanceUID') );
 CREATE TABLE 'Studies' (
-  'StudyInstanceUID' VARCHAR(255) NOT NULL ,
+  'StudyInstanceUID' VARCHAR(64) NOT NULL ,
   'PatientsUID' INT NOT NULL ,
   'StudyID' VARCHAR(255) NULL ,
   'StudyDate' DATE NULL ,
@@ -51,7 +51,7 @@ CREATE TABLE 'Studies' (
   'ModalitiesInStudy' VARCHAR(255) NULL ,
   'InstitutionName' VARCHAR(255) NULL ,
   'ReferringPhysician' VARCHAR(255) NULL ,
-  'PerformingPysiciansName' VARCHAR(255) NULL ,
+  'PerformingPhysiciansName' VARCHAR(255) NULL ,
   'StudyDescription' VARCHAR(255) NULL ,
   PRIMARY KEY ('StudyInstanceUID') );
 

+ 250 - 0
Libs/DICOM/Core/ctkDICOMIndexerBase.cpp

@@ -0,0 +1,250 @@
+/*=========================================================================
+
+  Library:   CTK
+ 
+  Copyright (c) 2010  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.commontk.org/LICENSE
+
+  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 <QSqlQuery>
+#include <QSqlRecord>
+#include <QVariant>
+#include <QDate>
+#include <QStringList>
+#include <QSet>
+#include <QFile>
+#include <QDirIterator>
+#include <QFileInfo>
+#include <QDebug>
+
+// ctkDICOM includes
+#include "ctkDICOMIndexerBase.h"
+
+#include "ctkLogger.h"
+
+// DCMTK includes
+#ifndef WIN32
+  #define HAVE_CONFIG_H 
+#endif
+#include <dcmtk/dcmdata/dcfilefo.h>
+#include <dcmtk/dcmdata/dcfilefo.h>
+#include <dcmtk/dcmdata/dcdeftag.h>
+#include <dcmtk/dcmdata/dcdatset.h>
+#include <dcmtk/ofstd/ofcond.h>
+#include <dcmtk/ofstd/ofstring.h>
+#include <dcmtk/ofstd/ofstd.h>        /* for class OFStandard */
+#include <dcmtk/dcmdata/dcddirif.h>   /* for class DicomDirInterface */
+
+static ctkLogger logger ( "org.commontk.dicom.DICOMIndexerBase" );
+
+//------------------------------------------------------------------------------
+class ctkDICOMIndexerBasePrivate: public ctkPrivate<ctkDICOMIndexerBase>
+{
+public:
+  ctkDICOMIndexerBasePrivate();
+  ~ctkDICOMIndexerBasePrivate();
+  QSqlDatabase db;
+
+};
+
+//------------------------------------------------------------------------------
+// ctkDICOMIndexerBasePrivate methods
+
+//------------------------------------------------------------------------------
+ctkDICOMIndexerBasePrivate::ctkDICOMIndexerBasePrivate()
+{
+}
+
+//------------------------------------------------------------------------------
+ctkDICOMIndexerBasePrivate::~ctkDICOMIndexerBasePrivate()
+{
+}
+
+//------------------------------------------------------------------------------
+// ctkDICOMIndexerBase methods
+
+//------------------------------------------------------------------------------
+ctkDICOMIndexerBase::ctkDICOMIndexerBase()
+{
+}
+
+//------------------------------------------------------------------------------
+ctkDICOMIndexerBase::~ctkDICOMIndexerBase()
+{
+}
+
+void ctkDICOMIndexerBase::setDatabase ( QSqlDatabase database ) {
+  CTK_D(ctkDICOMIndexerBase);
+  d->db = database;
+}
+
+void ctkDICOMIndexerBase::insert ( DcmDataset *dataset ) {
+  this->insert ( dataset, QString() );
+}
+
+void ctkDICOMIndexerBase::insert ( DcmDataset *dataset, QString filename ) {
+  CTK_D(ctkDICOMIndexerBase);
+
+  // Check to see if the file has already been loaded
+  QSqlQuery fileExists ( d->db );
+  fileExists.prepare("SELECT InsertTimestamp FROM Images WHERE Filename == ?"); 
+  fileExists.bindValue(0,filename);
+  fileExists.exec();
+  if ( fileExists.next() && QFileInfo(filename).lastModified() < QDateTime::fromString(fileExists.value(0).toString(),Qt::ISODate) )
+    {
+    logger.debug ( "File " + filename + " already added" );
+    return;
+    }
+
+    OFString patientsName, patientID, patientsBirthDate, patientsBirthTime, patientsSex,
+      patientComments, patientsAge;
+
+    OFString studyInstanceUID, studyID, studyDate, studyTime,
+      accessionNumber, modalitiesInStudy, institutionName, performingPhysiciansName, referringPhysician, studyDescription;
+
+    OFString seriesInstanceUID, seriesDate, seriesTime,
+      seriesDescription, bodyPartExamined, frameOfReferenceUID,
+      contrastAgent, scanningSequence;
+    OFString instanceNumber;
+
+    Sint32 seriesNumber = 0, acquisitionNumber = 0, echoNumber = 0, temporalPosition = 0;
+
+    //If the following fields can not be evaluated, cancel evaluation of the DICOM file
+    dataset->findAndGetOFString(DCM_PatientsName, patientsName);
+    dataset->findAndGetOFString(DCM_StudyInstanceUID, studyInstanceUID);
+    dataset->findAndGetOFString(DCM_SeriesInstanceUID, seriesInstanceUID);
+    dataset->findAndGetOFString(DCM_PatientID, patientID);
+
+    dataset->findAndGetOFString(DCM_PatientsBirthDate, patientsBirthDate);
+    dataset->findAndGetOFString(DCM_PatientsBirthTime, patientsBirthTime);
+    dataset->findAndGetOFString(DCM_PatientsSex, patientsSex);
+    dataset->findAndGetOFString(DCM_PatientsAge, patientsAge);
+    dataset->findAndGetOFString(DCM_PatientComments, patientComments);
+    dataset->findAndGetOFString(DCM_StudyID, studyID);
+    dataset->findAndGetOFString(DCM_StudyDate, studyDate);
+    dataset->findAndGetOFString(DCM_StudyTime, studyTime);
+    dataset->findAndGetOFString(DCM_AccessionNumber, accessionNumber);
+    dataset->findAndGetOFString(DCM_ModalitiesInStudy, modalitiesInStudy);
+    dataset->findAndGetOFString(DCM_InstitutionName, institutionName);
+    dataset->findAndGetOFString(DCM_PerformingPhysiciansName, performingPhysiciansName);
+    dataset->findAndGetOFString(DCM_ReferringPhysiciansName, referringPhysician);
+    dataset->findAndGetOFString(DCM_StudyDescription, studyDescription);
+
+    dataset->findAndGetOFString(DCM_SeriesDate, seriesDate);
+    dataset->findAndGetOFString(DCM_SeriesTime, seriesTime);
+    dataset->findAndGetOFString(DCM_SeriesDescription, seriesDescription);
+    dataset->findAndGetOFString(DCM_BodyPartExamined, bodyPartExamined);
+    dataset->findAndGetOFString(DCM_FrameOfReferenceUID, frameOfReferenceUID);
+    dataset->findAndGetOFString(DCM_ContrastBolusAgent, contrastAgent);
+    dataset->findAndGetOFString(DCM_ScanningSequence, scanningSequence);
+
+    dataset->findAndGetSint32(DCM_SeriesNumber, seriesNumber);
+    dataset->findAndGetSint32(DCM_AcquisitionNumber, acquisitionNumber);
+    dataset->findAndGetSint32(DCM_EchoNumbers, echoNumber);
+    dataset->findAndGetSint32(DCM_TemporalPositionIdentifier, temporalPosition);
+
+    //Check if patient is already present in the db
+    QSqlQuery check_exists_query(d->db);
+    check_exists_query.prepare ( "SELECT * FROM Patients WHERE PatientID = ? AND PatientsName = ?" );
+    check_exists_query.bindValue ( 0, QString ( patientID.c_str() ) );
+    check_exists_query.bindValue ( 1, QString ( patientsName.c_str() ) );
+    check_exists_query.exec();
+
+    //The patient UID is a unique number within the database, generated by the sqlite autoincrement
+    int patientUID = -1;
+    if (check_exists_query.next())
+      {
+      patientUID = check_exists_query.value(check_exists_query.record().indexOf("UID")).toInt();
+      }
+    else
+      {
+      // Insert it
+      QSqlQuery statement ( d->db );
+      statement.prepare ( "INSERT INTO Patients ('UID', 'PatientsName', 'PatientID', 'PatientsBirthDate', 'PatientsBirthTime', 'PatientsSex', 'PatientsAge', 'PatientsComments' ) values ( NULL, ?, ?, ?, ?, ?, ?, ? )" );
+      statement.bindValue ( 0, QString ( patientsName.c_str() ) );
+      statement.bindValue ( 1, QString ( patientID.c_str() ) );
+      statement.bindValue ( 2, QString ( patientsBirthDate.c_str() ) );
+      statement.bindValue ( 3, QString ( patientsBirthTime.c_str() ) );
+      statement.bindValue ( 4, QString ( patientsSex.c_str() ) );
+      statement.bindValue ( 5, QString ( patientsAge.c_str() ) );
+      statement.bindValue ( 6, QString ( patientComments.c_str() ) );
+      statement.exec ();
+      patientUID = statement.lastInsertId().toInt();
+      logger.debug ( "New patient inserted: " + QString().setNum ( patientUID ) );
+      }
+
+    check_exists_query.prepare ( "SELECT * FROM Studies WHERE StudyInstanceUID = ?" );
+    check_exists_query.bindValue ( 0, QString ( studyInstanceUID.c_str() ) );
+    check_exists_query.exec();
+    if(!check_exists_query.next())
+      {
+        QSqlQuery statement ( d->db );
+        statement.prepare ( "INSERT INTO Studies ( 'StudyInstanceUID', 'PatientsUID', 'StudyID', 'StudyDate', 'StudyTime', 'AccessionNumber', 'ModalitiesInStudy', 'InstitutionName', 'ReferringPhysician', 'PerformingPhysiciansName', 'StudyDescription' ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )" );
+        statement.bindValue ( 0, QString ( studyInstanceUID.c_str() ) );
+        statement.bindValue ( 1, patientUID );
+        statement.bindValue ( 2, QString ( studyID.c_str() ) );
+        statement.bindValue ( 3, QDate::fromString ( studyDate.c_str(), "yyyyMMdd" ) );
+        statement.bindValue ( 4, QString ( studyTime.c_str() ) );
+        statement.bindValue ( 5, QString ( accessionNumber.c_str() ) );
+        statement.bindValue ( 6, QString ( modalitiesInStudy.c_str() ) );
+        statement.bindValue ( 7, QString ( institutionName.c_str() ) );
+        statement.bindValue ( 8, QString ( referringPhysician.c_str() ) );
+        statement.bindValue ( 9, QString ( performingPhysiciansName.c_str() ) );
+        statement.bindValue ( 10, QString ( studyDescription.c_str() ) );
+        statement.exec();
+      }
+
+
+    check_exists_query.prepare ( "SELECT * FROM Series WHERE SeriesInstanceUID = ?" );
+    check_exists_query.bindValue ( 0, QString ( seriesInstanceUID.c_str() ) );
+    check_exists_query.exec();
+    if(!check_exists_query.next())
+      {
+      QSqlQuery statement ( d->db );
+      statement.prepare ( "INSERT INTO Studies ( 'SeriesInstanceUID', 'StudyInstanceUID', 'SeriesNumber', 'SeriesDate', 'SeriesTime', 'SeriesDescription', 'BodyPartExamined', 'FrameOfReferenceUID', 'AcquisitionNumber', 'ContrastAgent', 'ScanningSequence', 'EchoNumber', 'TemporalPosition' ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )" );
+      statement.bindValue ( 0, QString ( seriesInstanceUID.c_str() ) );
+      statement.bindValue ( 1, QString ( studyInstanceUID.c_str() ) );
+      statement.bindValue ( 2, seriesNumber );
+      statement.bindValue ( 3, QString ( seriesDate.c_str() ) );
+      statement.bindValue ( 4, QDate::fromString ( seriesTime.c_str(), "yyyyMMdd" ) );
+      statement.bindValue ( 5, QString ( seriesDescription.c_str() ) );
+      statement.bindValue ( 6, QString ( bodyPartExamined.c_str() ) );
+      statement.bindValue ( 7, QString ( frameOfReferenceUID.c_str() ) );
+      statement.bindValue ( 8, acquisitionNumber );
+      statement.bindValue ( 9, QString ( contrastAgent.c_str() ) );
+      statement.bindValue ( 10, QString ( scanningSequence.c_str() ) );
+      statement.bindValue ( 11, echoNumber );
+      statement.bindValue ( 12, temporalPosition );
+      statement.exec();
+      }
+    if ( !filename.isEmpty() )
+      {
+      check_exists_query.prepare ( "SELECT * FROM Images WHERE Filename = ?" );
+      check_exists_query.bindValue ( 0, filename );
+      check_exists_query.exec();
+      if(!check_exists_query.next())
+        {
+        QSqlQuery statement ( d->db );
+        statement.prepare ( "INSERT INTO Images ( 'Filename', 'SeriesInstanceUID', 'InsertTimestamp' ) VALUES ( ?, ?, ? )" );
+        statement.bindValue ( 0, filename );
+        statement.bindValue ( 1, QString ( seriesInstanceUID.c_str() ) );
+        statement.bindValue ( 2, QDateTime::currentDateTime() );
+        statement.exec();
+        }
+      }
+}
+
+

+ 56 - 0
Libs/DICOM/Core/ctkDICOMIndexerBase.h

@@ -0,0 +1,56 @@
+/*=========================================================================
+
+  Library:   CTK
+ 
+  Copyright (c) 2010
+
+  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.commontk.org/LICENSE
+
+  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.
+ 
+=========================================================================*/
+
+#ifndef __ctkDICOMIndexerBase_h
+#define __ctkDICOMIndexerBase_h
+
+// Qt includes 
+#include <QObject>
+#include <QSqlDatabase>
+
+// CTK includes
+#include <ctkPimpl.h>
+
+#include "CTKDICOMCoreExport.h"
+
+class ctkDICOMIndexerBasePrivate;
+class DcmDataset;
+
+class CTK_DICOM_CORE_EXPORT ctkDICOMIndexerBase : public QObject
+{
+  Q_OBJECT
+public:
+  explicit ctkDICOMIndexerBase();
+  virtual ~ctkDICOMIndexerBase();
+  void setDatabase ( QSqlDatabase database );
+  /**
+   * Will create an entry in the appropriate tables for this dataset.
+   */
+  void insert ( DcmDataset* dataset, QString filename );
+  /**
+   * Insert into the database if not already exsting.
+   */
+  void insert ( DcmDataset *dataset );
+
+private:
+  CTK_DECLARE_PRIVATE(ctkDICOMIndexerBase);
+};
+
+#endif

+ 12 - 7
Libs/DICOM/Core/ctkDICOMQuery.cpp

@@ -52,7 +52,7 @@
 
 #include <dcmtk/dcmnet/scu.h>
 
-static ctkLogger logger ( "org.commontk.core.Logger" );
+static ctkLogger logger ( "org.commontk.dicom.DICOMQuery" );
 
 //------------------------------------------------------------------------------
 class ctkDICOMQueryPrivate: public ctkPrivate<ctkDICOMQuery>
@@ -91,11 +91,16 @@ static void QueryCallback (void *callbackData,
                            T_DIMSE_C_FindRQ* /*request*/, 
                            int /*responseCount*/, 
                            T_DIMSE_C_FindRSP* /*rsp*/, 
-                           DcmDataset *responseIdentifiers) {
-  ctkDICOMQueryPrivate* d = (ctkDICOMQueryPrivate*) callbackData;
-  OFString StudyDescription;
-  responseIdentifiers->findAndGetOFString ( DCM_StudyDescription, StudyDescription );
-  logger.debug ( QString ( "Found study description: " ) + QString ( StudyDescription.c_str() ) );
+                           DcmDataset *dataset) {
+  ctkDICOMQuery* query = (ctkDICOMQuery*) callbackData;
+  OFString StudyDescription, PatientID, PatientsName;
+  dataset->findAndGetOFString ( DCM_StudyDescription, StudyDescription );
+  dataset->findAndGetOFString ( DCM_PatientID, PatientID );
+  dataset->findAndGetOFString ( DCM_PatientsName, PatientsName );
+  logger.debug ( "Found study description: " + QString ( StudyDescription.c_str() ) 
+                 + " for Patient: " + QString ( PatientsName.c_str() )
+                 + "/" + QString ( PatientID.c_str() ) );
+  query->insert ( dataset );
 }
 
 //------------------------------------------------------------------------------
@@ -229,7 +234,7 @@ void ctkDICOMQuery::query(QSqlDatabase database )
   d->query->insertEmptyElement ( DCM_NumberOfStudyRelatedSeries ); // Number of images in the series
   d->query->putAndInsertString ( DCM_QueryRetrieveLevel, "STUDY" );
 
-  OFCondition status = d->SCU.sendFINDRequest ( 0, d->query, QueryCallback, (void*)d );
+  OFCondition status = d->SCU.sendFINDRequest ( 0, d->query, QueryCallback, (void*)this );
   if ( status.good() )
     {
     logger.debug ( "Find succeded" );

+ 2 - 1
Libs/DICOM/Core/ctkDICOMQuery.h

@@ -29,9 +29,10 @@
 #include <ctkPimpl.h>
 
 #include "CTKDICOMCoreExport.h"
+#include "ctkDICOMIndexerBase.h"
 
 class ctkDICOMQueryPrivate;
-class CTK_DICOM_CORE_EXPORT ctkDICOMQuery : public QObject
+class CTK_DICOM_CORE_EXPORT ctkDICOMQuery : public ctkDICOMIndexerBase
 {
   Q_OBJECT
   Q_PROPERTY(QString callingAETitle READ callingAETitle WRITE setCallingAETitle);