Преглед на файлове

ENH: first version of indexer, extended sql schema

Marco Nolden преди 15 години
родител
ревизия
b2dbd9d1c9
променени са 4 файла, в които са добавени 393 реда и са изтрити 0 реда
  1. 35 0
      Libs/DICOM/Applications/ctkDicomIndex.cxx
  2. 2 0
      Libs/DICOM/Core/Resources/dicom-schema.sql
  3. 332 0
      Libs/DICOM/Core/qCTKDCMTKIndexer.cxx
  4. 24 0
      Libs/DICOM/Core/qCTKDCMTKIndexer.h

+ 35 - 0
Libs/DICOM/Applications/ctkDicomIndex.cxx

@@ -0,0 +1,35 @@
+
+// QT includes
+#include <QApplication>
+#include <QPushButton>
+#include <QTextStream>
+// STD includes
+//#include <cstdlib>
+
+#include <qCTKDCMTKIndexer.h>
+#include <qCTKDCMTK.h>
+
+
+int main(int argc, char** argv)
+{
+  QApplication app(argc, argv);
+  QTextStream out(stdout);
+
+  qCTKDCMTKIndexer idx;
+
+  qCTKDCMTK myCTK;
+  if ( myCTK.openDatabase( argv[1]) )
+    {
+    out << "open db success\n";
+    /// make sure it is empty and properly initialized
+    myCTK.initializeDatabase();
+    idx.AddDirectory(myCTK.database(),argv[2]);
+    myCTK.closeDatabase();
+    }
+  else
+    { 
+    out << "ERROR: " ;
+    out << myCTK.GetLastError();
+    }
+  return EXIT_SUCCESS;
+}

+ 2 - 0
Libs/DICOM/Core/Resources/dicom-schema.sql

@@ -22,6 +22,7 @@ CREATE TABLE 'Patients' (
   'PatientsBirthDate' DATE NULL ,
   'PatientsBirthTime' TIME NULL ,
   'PatientsSex' varchar(1) NULL ,
+  'PatientsAge' varchar(10) NULL ,
   'PatientsComments' VARCHAR(255) NULL );
 CREATE TABLE 'Series' (
   'SeriesInstanceUID' VARCHAR(255) NOT NULL ,
@@ -47,6 +48,7 @@ CREATE TABLE 'Studies' (
   'AccessionNumber' VARCHAR(255) NULL ,
   'ModalitiesInStudy' VARCHAR(255) NULL ,
   'ReferringPhysician' VARCHAR(255) NULL ,
+  'PerformingPysiciansName' VARCHAR(255) NULL ,
   'StudyDescription' VARCHAR(255) NULL ,
   PRIMARY KEY ('StudyInstanceUID') );
 

+ 332 - 0
Libs/DICOM/Core/qCTKDCMTKIndexer.cxx

@@ -0,0 +1,332 @@
+#include "qCTKDCMTKIndexer.h"
+
+///
+/// DCMTK includes
+#ifndef WIN32
+  #define HAVE_CONFIG_H 
+#endif
+#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 */
+
+#include <QSqlQuery>
+#include <QSqlRecord>
+#include <QVariant>
+#include <QDate>
+
+#define MITK_ERROR std::cout
+#define MITK_INFO std::cout
+
+class qCTKDCMTKIndexerPrivate: public qCTKPrivate<qCTKDCMTKIndexer>
+{
+public:
+  qCTKDCMTKIndexerPrivate();
+  ~qCTKDCMTKIndexerPrivate();
+
+};
+
+qCTKDCMTKIndexerPrivate::qCTKDCMTKIndexerPrivate()
+{
+}
+
+qCTKDCMTKIndexerPrivate::~qCTKDCMTKIndexerPrivate()
+{
+}
+
+qCTKDCMTKIndexer::qCTKDCMTKIndexer()
+{
+}
+
+qCTKDCMTKIndexer::~qCTKDCMTKIndexer()
+{
+}
+
+void qCTKDCMTKIndexer::AddDirectory(QSqlDatabase database, const QString& directoryName)  
+{
+  QSqlDatabase db = database;
+  const std::string src_directory(directoryName.toStdString());
+  // db.transaction();
+
+  OFList<OFString> originalDcmtkFileNames;
+  OFList<OFString> dcmtkFileNames;
+  OFStandard::searchDirectoryRecursively(src_directory.c_str(), originalDcmtkFileNames, NULL, NULL);
+
+  // hack to reverse list of filenames (not neccessary when image loading works correctly)
+  for ( OFListIterator(OFString) iter = originalDcmtkFileNames.begin(); iter != originalDcmtkFileNames.end(); ++iter )
+  {
+    dcmtkFileNames.push_front( *iter );
+  }
+
+  DcmFileFormat fileformat;
+
+  OFListIterator(OFString) iter = dcmtkFileNames.begin();
+  OFListIterator(OFString) last = dcmtkFileNames.end();
+
+  if(iter == last) return;
+
+  QSqlQuery query(database);
+
+  /* iterate over all input filenames */
+  OFString lastPatientID = "", lastPatientsName = "", lastPatientsBirthDate = "", lastStudyInstanceUID = "", lastSeriesInstanceUID = "";
+  int lastPatientUID = -1;
+
+  while (iter != last)
+  {
+    std::string filename((*iter).c_str());
+    MITK_INFO << filename << "\n";
+    OFCondition status = fileformat.loadFile(filename.c_str());
+    ++iter;
+
+    if (!status.good())
+    {
+      MITK_ERROR << "Could not load " << filename << "\nDCMTK says: " << status.text();
+      continue;
+    }
+
+    OFString patientsName = "", patientID = "", patientsBirthDate = "", patientsBirthTime = "", patientsSex = "",
+      patientComments = "", patientsAge = "";
+
+    OFString studyInstanceUID = "", studyID = "", studyDate = "", studyTime = "",
+      accessionNumber = "", modalitiesInStudy = "", performingPhysiciansName = "", referringPhysician = "", studyDescription = "";
+
+    OFString seriesInstanceUID = "", seriesDate = "", seriesTime = "",
+      seriesDescription = "", bodyPartExamined = "", frameOfReferenceUID = "",
+      contrastAgent = "", scanningSequence = "";
+
+    Sint32 seriesNumber = 0, acquisitionNumber = 0, echoNumber = 0, temporalPosition = 0;
+
+    //The patient UID is a unique number within the database, generated by the sqlite autoincrement
+    int patientUID = -1;
+
+    //If the following fields can not be evaluated, cancel evaluation of the DICOM file
+    if (!fileformat.getDataset()->findAndGetOFString(DCM_PatientsName, patientsName).good())
+    {
+      MITK_ERROR << "Could not read DCM_PatientsName from " << filename;
+      continue;
+    }
+
+    if (!fileformat.getDataset()->findAndGetOFString(DCM_StudyInstanceUID, studyInstanceUID).good())
+    {
+      MITK_ERROR << "Could not read DCM_StudyInstanceUID from " << filename;
+      continue;
+    }
+
+    if (!fileformat.getDataset()->findAndGetOFString(DCM_SeriesInstanceUID, seriesInstanceUID).good())
+    {
+      MITK_ERROR << "Could not read DCM_SeriesInstanceUID from " << filename;
+      continue;
+    }
+
+    fileformat.getDataset()->findAndGetOFString(DCM_PatientID, patientID);
+    fileformat.getDataset()->findAndGetOFString(DCM_PatientsBirthDate, patientsBirthDate);
+    fileformat.getDataset()->findAndGetOFString(DCM_PatientsBirthTime, patientsBirthTime);
+    fileformat.getDataset()->findAndGetOFString(DCM_PatientsSex, patientsSex);
+    fileformat.getDataset()->findAndGetOFString(DCM_PatientsAge, patientsAge);
+    fileformat.getDataset()->findAndGetOFString(DCM_PatientComments, patientComments);
+    fileformat.getDataset()->findAndGetOFString(DCM_StudyID, studyID);
+    fileformat.getDataset()->findAndGetOFString(DCM_StudyDate, studyDate);
+    fileformat.getDataset()->findAndGetOFString(DCM_StudyTime, studyTime);
+    fileformat.getDataset()->findAndGetOFString(DCM_AccessionNumber, accessionNumber);
+    fileformat.getDataset()->findAndGetOFString(DCM_ModalitiesInStudy, modalitiesInStudy);
+    fileformat.getDataset()->findAndGetOFString(DCM_PerformingPhysiciansName, performingPhysiciansName);
+    fileformat.getDataset()->findAndGetOFString(DCM_ReferringPhysiciansName, referringPhysician);
+    fileformat.getDataset()->findAndGetOFString(DCM_StudyDescription, studyDescription);
+
+    fileformat.getDataset()->findAndGetOFString(DCM_SeriesDate, seriesDate);
+    fileformat.getDataset()->findAndGetOFString(DCM_SeriesTime, seriesTime);
+    fileformat.getDataset()->findAndGetOFString(DCM_SeriesDescription, seriesDescription);
+    fileformat.getDataset()->findAndGetOFString(DCM_BodyPartExamined, bodyPartExamined);
+    fileformat.getDataset()->findAndGetOFString(DCM_FrameOfReferenceUID, frameOfReferenceUID);
+    fileformat.getDataset()->findAndGetOFString(DCM_ContrastBolusAgent, contrastAgent);
+    fileformat.getDataset()->findAndGetOFString(DCM_ScanningSequence, scanningSequence);
+
+    fileformat.getDataset()->findAndGetSint32(DCM_SeriesNumber, seriesNumber);
+    fileformat.getDataset()->findAndGetSint32(DCM_AcquisitionNumber, acquisitionNumber);
+    fileformat.getDataset()->findAndGetSint32(DCM_EchoNumbers, echoNumber);
+    fileformat.getDataset()->findAndGetSint32(DCM_TemporalPositionIdentifier, temporalPosition);
+
+    MITK_INFO << "Adding new items to database:";
+    MITK_INFO << "studyID: " << studyID;
+    MITK_INFO << "seriesInstanceUID: " << seriesInstanceUID;
+    MITK_INFO << "Patient's Name: " << patientsName;
+
+    //-----------------------
+    //Add Patient to Database
+    //-----------------------
+
+    //Speed up: Check if patient is the same as in last file; very probable, as all images belonging to a study have the same patient
+    if(lastPatientID.compare(patientID) || lastPatientsBirthDate.compare(patientsBirthDate) || lastPatientsName.compare(patientsName))
+    {
+      //Check if patient is already present in the db
+      QSqlQuery check_exists_query(database);
+      std::stringstream check_exists_query_string;
+      check_exists_query_string << "SELECT * FROM Patients WHERE PatientID = '" << patientID << "'";
+      check_exists_query.exec(check_exists_query_string.str().c_str());
+      //check_exists_query_string.flush();
+
+      bool patientExists = false;
+
+      while (check_exists_query.next())
+      {
+        patientExists = true;
+        QString checkPatientsName = check_exists_query.value(check_exists_query.record().indexOf("PatientsName")).toString();
+        if(checkPatientsName.toStdString().compare(patientsName.c_str())) patientExists = false;
+
+        QString checkPatientsBirthDate = check_exists_query.value(check_exists_query.record().indexOf("PatientsBirthDate")).toString();
+        if(checkPatientsBirthDate.toStdString().compare(patientsBirthDate.c_str())) patientExists = false;
+
+        if(patientExists)
+        {
+          patientUID = check_exists_query.value(check_exists_query.record().indexOf("UID")).toInt();
+          break;
+        }
+      }
+
+      if(!patientExists)
+      {
+
+        std::stringstream query_string;
+
+        query_string << "INSERT INTO Patients VALUES( NULL,'" << patientsName << "','" << patientID << "','" << patientsBirthDate << "','"
+          << patientsBirthTime << "','" << patientsSex << "','" << patientsAge << "','" << patientComments << "')";
+
+        query.exec(query_string.str().c_str());
+
+        patientUID = query.lastInsertId().toInt();
+        MITK_INFO << "Query result: " << patientUID << "\n";
+
+
+      }
+    }
+    else patientUID = lastPatientUID;
+
+    lastPatientUID = patientUID;
+    lastPatientID = patientID;
+    lastPatientsBirthDate = patientsBirthDate;
+    lastPatientsName = patientsName;
+
+    //---------------------
+    //Add Study to Database
+    //---------------------
+
+    if(lastStudyInstanceUID.compare(studyInstanceUID))
+    {
+      QSqlQuery check_exists_query(database);
+      std::stringstream check_exists_query_string;
+      check_exists_query_string << "SELECT * FROM Studies WHERE StudyInstanceUID = '" << studyInstanceUID << "'";
+      check_exists_query.exec(check_exists_query_string.str().c_str());
+
+      if(!check_exists_query.next())
+      {
+
+        std::stringstream query_string;
+
+        query_string << "INSERT INTO Studies VALUES('"
+          << studyInstanceUID << "','" << patientUID << "','" << studyID << "','"
+          << QDate::fromString(studyDate.c_str(), "yyyyMMdd").toString("yyyy-MM-dd").toStdString() << "','"
+          << studyTime << "','" << accessionNumber << "','" << modalitiesInStudy << "','" << referringPhysician << "','" << performingPhysiciansName << "','" << studyDescription << "')";
+
+        query.exec(query_string.str().c_str());
+      }
+    }
+
+    lastStudyInstanceUID = studyInstanceUID;
+
+    //----------------------
+    //Add Series to Database
+    //----------------------
+
+    if(lastSeriesInstanceUID.compare(seriesInstanceUID))
+    {
+
+      QSqlQuery check_exists_query(database);
+      std::stringstream check_exists_query_string;
+      check_exists_query_string << "SELECT * FROM Series WHERE SeriesInstanceUID = '" << seriesInstanceUID << "'";
+      check_exists_query.exec(check_exists_query_string.str().c_str());
+
+      if(!check_exists_query.next())
+      {
+
+        std::stringstream query_string;
+
+        query_string << "INSERT INTO Series VALUES('"
+          << seriesInstanceUID << "','" << studyInstanceUID << "','" << (int) seriesNumber << "','"
+          << QDate::fromString(seriesDate.c_str(), "yyyyMMdd").toString("yyyy-MM-dd").toStdString() << "','"
+          << seriesTime << "','" << seriesDescription << "','" << bodyPartExamined << "','"
+          << frameOfReferenceUID << "','" << (int) acquisitionNumber << "','" << contrastAgent << "','"
+          << scanningSequence << "','" << (int) echoNumber << "','" << (int) temporalPosition << "')";
+
+        query.exec(query_string.str().c_str());
+      }
+    }
+
+    lastSeriesInstanceUID = seriesInstanceUID;
+
+
+    //----------------------------------
+    //Move file to destination directory
+    //----------------------------------
+    /*
+    // This depends on Poco and should be converted to Qt code
+
+    Poco::File currentFile(filename);
+    Poco::Path currentFilePath(filename);
+    MITK_INFO << "currentFilePath.getFileName(): " << currentFilePath.getFileName() << "\n";
+
+    if(moveFiles)
+    {
+      std::stringstream destDirectoryPath;
+      if((dest_directory[dest_directory.length()-1] != '/') && (dest_directory[dest_directory.length()-1] != '\\'))
+        destDirectoryPath << dest_directory << Poco::Path::separator() << seriesInstanceUID.c_str();
+      else
+        destDirectoryPath << dest_directory << seriesInstanceUID.c_str();
+
+      MITK_INFO << "last symbol: " << dest_directory[dest_directory.length()-1]
+      << "\ndestDirectoryPath: " << destDirectoryPath.str() << "\n";
+
+      Poco::File directory(destDirectoryPath.str());
+
+      if (!directory.exists()) directory.createDirectory();
+
+      destDirectoryPath << Poco::Path::separator() << currentFilePath.getFileName();
+
+      currentFile.moveTo(destDirectoryPath.str());
+      //for testing only: copy file instead of moving
+      //currentFile.copyTo(destDirectoryPath.str());
+    }
+    */
+    //------------------------
+    //Add Filename to Database
+    //------------------------
+
+//    std::stringstream relativeFilePath;
+//    relativeFilePath << seriesInstanceUID.c_str() << "/" << currentFilePath.getFileName();
+
+    QSqlQuery check_exists_query(database);
+    std::stringstream check_exists_query_string;
+//    check_exists_query_string << "SELECT * FROM Images WHERE Filename = '" << relativeFilePath.str() << "'";
+    check_exists_query_string << "SELECT * FROM Images WHERE Filename = '" << filename << "'";
+    check_exists_query.exec(check_exists_query_string.str().c_str());
+
+    if(!check_exists_query.next())
+    {
+      std::stringstream query_string;
+
+      //To save absolute path: destDirectoryPath.str()
+      query_string << "INSERT INTO Images VALUES('"
+        << /*relativeFilePath.str()*/ filename << "','" << seriesInstanceUID << "')";
+
+      query.exec(query_string.str().c_str());
+    }
+  }
+
+  // db.commit();
+  // db.close();
+
+}
+

+ 24 - 0
Libs/DICOM/Core/qCTKDCMTKIndexer.h

@@ -0,0 +1,24 @@
+#ifndef __qCTKDCMTKIndexer_h
+#define __qCTKDCMTKIndexer_h
+
+// QT includes 
+#include <QSqlDatabase>
+
+// qCTK includes
+#include <qCTKPimpl.h>
+
+#include "CTKDICOMCoreExport.h"
+
+class qCTKDCMTKIndexerPrivate;
+class Q_CTK_DICOM_CORE_EXPORT qCTKDCMTKIndexer  
+{
+public:
+  explicit qCTKDCMTKIndexer();
+  virtual ~qCTKDCMTKIndexer();
+  void AddDirectory(QSqlDatabase database, const QString& directoryName);
+
+private:
+  QCTK_DECLARE_PRIVATE(qCTKDCMTKIndexer);
+};
+
+#endif