浏览代码

Merge branch 'DICOMRetrieve'

Daniel Blezek 15 年之前
父节点
当前提交
d9e19c3a01

+ 1 - 1
Applications/ctkDICOMIndexer/ctkDICOMIndexerMain.cpp

@@ -59,7 +59,7 @@ int main(int argc, char** argv)
     return EXIT_FAILURE;
   }
 
-  QApplication app(argc, argv);
+  QCoreApplication app(argc, argv);
   QTextStream out(stdout);
 
   ctkDICOMIndexer idx;

+ 1 - 1
Applications/ctkDICOMQuery/ctkDICOMQueryMain.cpp

@@ -55,7 +55,7 @@ int main(int argc, char** argv)
     return EXIT_FAILURE;
   }
 
-  QApplication app(argc, argv);
+  QCoreApplication app(argc, argv);
   QTextStream out(stdout);
 
   ctkDICOM myCTK;

+ 45 - 0
Applications/ctkDICOMRetrieve/CMakeLists.txt

@@ -0,0 +1,45 @@
+PROJECT(ctkDICOMRetrieve)
+
+#
+# See CTK/CMake/ctkMacroBuildApp.cmake for details
+#
+  
+# Source files
+SET(KIT_SRCS
+  ctkDICOMRetrieveMain.cpp
+  )
+
+# Headers that should run through moc
+SET(KIT_MOC_SRCS
+  )
+
+# UI files
+SET(KIT_UI_FORMS
+)
+
+# Resources
+SET(KIT_resources
+)
+
+# Target libraries - See CMake/ctkMacroGetTargetLibraries.cmake
+# The following macro will read the target libraries from the file 'target_libraries.cmake'
+ctkMacroGetTargetLibraries(KIT_target_libraries)
+
+# Additional directories to include - Not that CTK_INCLUDE_LIBRARIES is already included
+SET(KIT_include_directories
+  )
+
+ctkMacroBuildApp(
+  NAME ${PROJECT_NAME}
+  INCLUDE_DIRECTORIES ${KIT_include_directories}
+  SRCS ${KIT_SRCS}
+  MOC_SRCS ${KIT_MOC_SRCS}
+  UI_FORMS ${KIT_UI_FORMS}
+  TARGET_LIBRARIES ${KIT_target_libraries}
+  RESOURCES ${KIT_resources}
+  )
+
+# Testing
+IF(BUILD_TESTING)
+  #ADD_SUBDIRECTORY(Testing)
+ENDIF(BUILD_TESTING)

+ 111 - 0
Applications/ctkDICOMRetrieve/ctkDICOMRetrieveMain.cpp

@@ -0,0 +1,111 @@
+/*=========================================================================
+
+  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.
+ 
+=========================================================================*/
+
+// Qt includes
+#include <QApplication>
+#include <QTextStream>
+
+// CTK includes
+#include <ctkDICOMRetrieve.h>
+#include <ctkDICOM.h>
+#include "ctkLogger.h"
+
+// STD includes
+#include <cstdlib>
+#include <iostream>
+#include <fstream>
+
+void print_usage()
+{
+  std::cerr << "Usage:\n";
+  std::cerr << "  ctkDICOMRetrieve SeriesUID OutputDirectory callingAETitle callingPort calledAETitle host calledPort\n";
+  return;
+}
+
+
+/**
+  *
+*/
+int main(int argc, char** argv)
+{
+  ctkLogger::configure();
+  ctkLogger logger ( "org.commontk.dicom.DICOMRetieveApp" );
+  logger.setDebug();
+
+  if (argc < 8)
+  {
+    print_usage();
+    return EXIT_FAILURE;
+  }
+
+  QCoreApplication app(argc, argv);
+  QTextStream out(stdout);
+
+  QString SeriesUID ( argv[1] );
+  QDir OutputDirectory ( argv[2] );
+  QString CallingAETitle ( argv[3] ); 
+  bool ok;
+  int CallingPort = QString ( argv[4] ).toInt ( &ok );
+  if ( !ok )
+    {
+    std::cerr << "Could not convert " << argv[4] << " to an integer for the callingPort" << std::endl;
+    print_usage();
+    return EXIT_FAILURE;
+    }
+
+  QString CalledAETitle ( argv[5] ); 
+  QString Host ( argv[6] ); 
+  int CalledPort = QString ( argv[7] ).toInt ( &ok );
+  if ( !ok )
+    {
+    std::cerr << "Could not convert " << argv[7] << " to an integer for the calledPoint" << std::endl;
+    print_usage();
+    return EXIT_FAILURE;
+    }
+
+  ctkDICOMRetrieve retrieve;
+  retrieve.setCallingAETitle ( CallingAETitle );
+  retrieve.setCallingPort ( CallingPort );
+  retrieve.setCalledAETitle ( CalledAETitle );
+  retrieve.setCalledPort ( CalledPort );
+  retrieve.setHost ( Host );
+
+  logger.info ( "SeriesUID: " + SeriesUID + "\n" 
+                + "OutputDirectory: " + OutputDirectory.absolutePath() + "\n"
+                + "CallingAETitle: " + CallingAETitle + "\n"
+                + "CallingPort: " + QString::number ( CallingPort ) + "\n"
+                + "CalledAEtitle: " + CalledAETitle + "\n"
+                + "Host: " + Host + "\n"
+                + "CalledPort: " + QString::number ( CalledPort ) + "\n" );
+
+
+  logger.info ( "Starting to retrieve" );
+  try
+    {
+    retrieve.retrieveSeries ( SeriesUID, OutputDirectory );
+    }
+  catch (std::exception e)
+    {
+    logger.error ( "Retrieve failed" );
+    return EXIT_FAILURE;
+    }
+  logger.info ( "Retrieve success" );
+  return EXIT_SUCCESS;
+}

+ 9 - 0
Applications/ctkDICOMRetrieve/target_libraries.cmake

@@ -0,0 +1,9 @@
+#
+# See CMake/ctkMacroGetTargetLibraries.cmake
+# 
+# This file should list the libraries required to build the current CTK application.
+# 
+
+SET(target_libraries
+  CTKDICOMCore
+  )

+ 1 - 0
CMakeLists.txt

@@ -290,6 +290,7 @@ SET(CTK_APPLICATIONS
   ctkDICOMIndexer:OFF
   ctkDICOMDemoSCU:OFF
   ctkDICOMQuery:OFF
+  ctkDICOMRetrieve:OFF
   ctkPluginBrowser:OFF
   )
   

+ 25 - 16
Libs/DICOM/Core/ctkDICOMQuery.cpp

@@ -174,8 +174,8 @@ void ctkDICOMQuery::query(QSqlDatabase database )
   transferSyntaxes.push_back ( UID_BigEndianExplicitTransferSyntax );
   transferSyntaxes.push_back ( UID_LittleEndianImplicitTransferSyntax );
 
-  // d->SCU.addPresentationContext ( UID_FINDStudyRootQueryRetrieveInformationModel, transferSyntaxes );
-  d->SCU.addPresentationContext ( UID_VerificationSOPClass, transferSyntaxes );
+  d->SCU.addPresentationContext ( UID_FINDStudyRootQueryRetrieveInformationModel, transferSyntaxes );
+  // d->SCU.addPresentationContext ( UID_VerificationSOPClass, transferSyntaxes );
   if ( !d->SCU.initNetwork().good() ) 
     {
     std::cerr << "Error initializing the network" << std::endl;
@@ -183,25 +183,14 @@ void ctkDICOMQuery::query(QSqlDatabase database )
     }
   logger.debug ( "Negotiating Association" );
   d->SCU.negotiateAssociation();
-  logger.debug ( "Sending Echo" );
-  OFString abstractSyntax;
-  OFString transferSyntax;
-  if ( d->SCU.sendECHORequest ( 0 ).good() )
-    {
-    std::cout << "ECHO Sucessful" << std::endl;
-    } 
-  else
-    {
-    std::cerr << "ECHO Failed" << std::endl;
-    }
+
   // Clear the query
   unsigned long elements = d->query->card();
   // Clean it out
   for ( unsigned long i = 0; i < elements; i++ ) 
     {
-    d->query->remove ( (unsigned long) 0 );
+    d->query->remove ( 0ul );
     }
-  d->query->insertEmptyElement ( DCM_QueryRetrieveLevel );
   d->query->insertEmptyElement ( DCM_PatientID );
   d->query->insertEmptyElement ( DCM_PatientsName );
   d->query->insertEmptyElement ( DCM_PatientsBirthDate );
@@ -229,7 +218,27 @@ void ctkDICOMQuery::query(QSqlDatabase database )
 
   FINDResponses *responses = new FINDResponses();
 
-  OFCondition status = d->SCU.sendFINDRequest ( 0, d->query, responses );
+  Uint16 presentationContex = 0;
+  presentationContex = d->SCU.findPresentationContextID ( UID_FINDStudyRootQueryRetrieveInformationModel, UID_LittleEndianExplicitTransferSyntax );
+  if ( presentationContex == 0 )
+    {
+  presentationContex = d->SCU.findPresentationContextID ( UID_FINDStudyRootQueryRetrieveInformationModel, UID_BigEndianExplicitTransferSyntax );
+    }
+  if ( presentationContex == 0 )
+    {
+    presentationContex = d->SCU.findPresentationContextID ( UID_FINDStudyRootQueryRetrieveInformationModel, UID_LittleEndianImplicitTransferSyntax );
+    }
+
+  if ( presentationContex == 0 )
+    {
+    logger.error ( "Failed to find acceptable presentation context" );
+    }
+  else
+    {
+    logger.info ( "Found useful presentation context" );
+    }
+
+  OFCondition status = d->SCU.sendFINDRequest ( presentationContex, d->query, responses );
   if ( status.good() )
     {
     logger.debug ( "Find succeded" );

+ 145 - 3
Libs/DICOM/Core/ctkDICOMRetrieve.cpp

@@ -50,8 +50,15 @@
 #include <dcmtk/ofstd/ofstd.h>        /* for class OFStandard */
 #include <dcmtk/dcmdata/dcddirif.h>   /* for class DicomDirInterface */
 
+#include <dcmtk/dcmjpeg/djdecode.h>  /* for dcmjpeg decoders */
+#include <dcmtk/dcmjpeg/djencode.h>  /* for dcmjpeg encoders */
+#include <dcmtk/dcmdata/dcrledrg.h>  /* for DcmRLEDecoderRegistration */
+#include <dcmtk/dcmdata/dcrleerg.h>  /* for DcmRLEEncoderRegistration */
+
 #include <dcmtk/dcmnet/scu.h>
 
+#include "dcmtk/oflog/oflog.h"
+
 static ctkLogger logger ( "org.commontk.dicom.DICOMRetrieve" );
 
 //------------------------------------------------------------------------------
@@ -66,7 +73,13 @@ public:
   int CallingPort;
   int CalledPort;
   DcmSCU SCU;
-  DcmDataset* query;
+  DcmDataset* parameters;
+
+  // do the retrieve, handling both series and study retrieves
+  enum RetrieveType { RetrieveSeries, RetrieveStudy };
+  void retrieve ( QString UID, QDir directory, RetrieveType retriveType );
+  
+
 };
 
 //------------------------------------------------------------------------------
@@ -75,15 +88,132 @@ public:
 //------------------------------------------------------------------------------
 ctkDICOMRetrievePrivate::ctkDICOMRetrievePrivate()
 {
-  query = new DcmDataset();
+  parameters = new DcmDataset();
 }
 
 //------------------------------------------------------------------------------
 ctkDICOMRetrievePrivate::~ctkDICOMRetrievePrivate()
 {
-  delete query;
+  delete parameters;
 }
 
+void ctkDICOMRetrievePrivate::retrieve ( QString UID, QDir directory, RetrieveType retriveType ) {
+
+  // Register the JPEG libraries in case we need them
+  //   (registration only happens once, so it's okay to call repeatedly)
+  // register global JPEG decompression codecs
+  DJDecoderRegistration::registerCodecs();
+  // register global JPEG compression codecs
+  DJEncoderRegistration::registerCodecs();
+  // register RLE compression codec
+  DcmRLEEncoderRegistration::registerCodecs();
+  // register RLE decompression codec
+  DcmRLEDecoderRegistration::registerCodecs();
+
+  // Set the DCMTK log level
+  log4cplus::Logger rootLogger = log4cplus::Logger::getRoot();
+  rootLogger.setLogLevel(log4cplus::DEBUG_LOG_LEVEL);
+
+  DcmSCU scu;
+  scu.setAETitle ( CallingAETitle.toStdString() );
+  scu.setPort ( CallingPort );
+  scu.setPeerAETitle ( CalledAETitle.toStdString() );
+  scu.setPeerHostName ( Host.toStdString() );
+  scu.setPeerPort ( CalledPort );
+
+  logger.error ( "Setting Transfer Syntaxes" );
+  OFList<OFString> transferSyntaxes;
+  transferSyntaxes.push_back ( UID_LittleEndianExplicitTransferSyntax );
+  transferSyntaxes.push_back ( UID_BigEndianExplicitTransferSyntax );
+  transferSyntaxes.push_back ( UID_LittleEndianImplicitTransferSyntax );
+  scu.addPresentationContext ( UID_FINDStudyRootQueryRetrieveInformationModel, transferSyntaxes );
+  scu.addPresentationContext ( UID_MOVEStudyRootQueryRetrieveInformationModel, transferSyntaxes );
+
+
+  if ( !scu.initNetwork().good() ) 
+    {
+    logger.error ( "Error initializing the network" );
+    return;
+    }
+  logger.debug ( "Negotiating Association" );
+  if ( !scu.negotiateAssociation().good() )
+    {
+    logger.error ( "Error negotiating association" );
+    return;
+    }
+  // Clear the query
+  unsigned long elements = this->parameters->card();
+  // Clean it out
+  for ( unsigned long i = 0; i < elements; i++ ) 
+    {
+    this->parameters->remove ( 0ul );
+    }
+  if ( retriveType == RetrieveSeries )
+    {
+    this->parameters->putAndInsertString ( DCM_QueryRetrieveLevel, "SERIES" );
+    this->parameters->putAndInsertString ( DCM_SeriesInstanceUID, UID.toStdString().c_str() );
+    } 
+  else
+    {
+    this->parameters->putAndInsertString ( DCM_QueryRetrieveLevel, "STUDY" );
+    this->parameters->putAndInsertString ( DCM_StudyInstanceUID, UID.toStdString().c_str() );  
+    }
+
+  MOVEResponses *responses = new MOVEResponses();
+  OFCondition status = scu.sendMOVERequest ( 0, this->parameters, responses );
+  if ( status.good() )
+    {
+    logger.debug ( "Find succeded" );
+
+    // Try to create the directory
+    directory.mkpath ( directory.absolutePath() );
+
+    // Write the responses out to disk
+    for ( OFListIterator(FINDResponse*) it = responses->begin(); it != responses->end(); it++ )
+      {
+      DcmDataset *dataset = (*it)->m_dataset;
+      if ( dataset != NULL )
+        {
+        // Save in correct directory
+        E_TransferSyntax output_transfersyntax = dataset->getOriginalXfer();
+        dataset->chooseRepresentation( output_transfersyntax, NULL );
+        
+        if ( !dataset->canWriteXfer( output_transfersyntax ) )
+          {
+          // Pick EXS_LittleEndianExplicit as our default
+          output_transfersyntax = EXS_LittleEndianExplicit;
+          }
+        
+        DcmXfer opt_oxferSyn( output_transfersyntax );
+        if ( !dataset->chooseRepresentation( opt_oxferSyn.getXfer(), NULL ).bad() )
+          {
+          DcmFileFormat* fileformat = new DcmFileFormat ( dataset );
+          
+          // Follow dcmdjpeg example
+          fileformat->loadAllDataIntoMemory();
+          OFString SOPInstanceUID;
+          dataset->findAndGetOFString ( DCM_SOPInstanceUID, SOPInstanceUID );
+          QFileInfo fi ( directory, QString ( SOPInstanceUID.c_str() ) );
+          logger.debug ( "Saving file: " + fi.absoluteFilePath() );
+          status = fileformat->saveFile ( fi.absoluteFilePath().toStdString().c_str(), opt_oxferSyn.getXfer() );
+          if ( !status.good() )
+            {
+            logger.error ( "Error saving file: " + fi.absoluteFilePath() + " Error is " + status.text() );
+            }
+          
+          delete fileformat;
+          }
+        }
+      }
+    }
+  else
+    {
+    logger.error ( "MOVE Request failed: " + QString ( status.text() ) );
+    }
+  delete responses;
+}
+
+
 
 
 //------------------------------------------------------------------------------
@@ -156,4 +286,16 @@ int ctkDICOMRetrieve::calledPort()
 
 //------------------------------------------------------------------------------
 void ctkDICOMRetrieve::retrieveSeries ( QString seriesInstanceUID, QDir directory ) {
+  CTK_D(ctkDICOMRetrieve);
+  logger.info ( "Starting retrieveSeries" );
+  d->retrieve ( seriesInstanceUID, directory, ctkDICOMRetrievePrivate::RetrieveSeries );
+  return;
 }
+
+void ctkDICOMRetrieve::retrieveStudy ( QString studyInstanceUID, QDir directory ) {
+  CTK_D(ctkDICOMRetrieve);
+  logger.info ( "Starting retrieveStudy" );
+  d->retrieve ( studyInstanceUID, directory, ctkDICOMRetrievePrivate::RetrieveStudy );
+  return;
+}
+

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

@@ -24,7 +24,6 @@
 
 // Qt includes 
 #include <QObject>
-#include <QSqlDatabase>
 #include <QDir>
 
 // CTK includes
@@ -54,8 +53,10 @@ public:
 
   // Could be a slot...
   void retrieveSeries ( QString seriesInstanceUID, QDir directory );
+  void retrieveStudy ( QString studyInstanceUID, QDir directory );
 
 private:
+  void retrieve ( QDir directory );
   CTK_DECLARE_PRIVATE(ctkDICOMRetrieve);
 
 };

+ 61 - 11
Utilities/CMake/FindDCMTK.cmake

@@ -106,6 +106,59 @@ FIND_LIBRARY( DCMTK_dcmdata_LIBRARY dcmdata
   NO_DEFAULT_PATH
 )
 
+FIND_PATH( DCMTK_dcmjpeg_INCLUDE_DIR djdecode.h
+  PATHS
+    ${DCMTK_DIR}/include/dcmjpeg
+    ${DCMTK_DIR}/include/dcmtk/dcmjpeg
+    ${DCMTK_DIR}/dcmjpeg
+    ${DCMTK_DIR}/dcmjpeg/include
+  NO_DEFAULT_PATH
+)
+
+FIND_LIBRARY( DCMTK_dcmjpeg_LIBRARY dcmjpeg
+  PATHS
+    ${DCMTK_DIR}/dcmjpeg/libsrc
+    ${DCMTK_DIR}/dcmjpeg/libsrc/Release
+    ${DCMTK_DIR}/dcmjpeg/libsrc/Debug
+    ${DCMTK_DIR}/dcmjpeg/Release
+    ${DCMTK_DIR}/dcmjpeg/Debug
+    ${DCMTK_DIR}/lib
+  NO_DEFAULT_PATH
+)
+
+FIND_LIBRARY( DCMTK_ijg12_LIBRARY ijg12
+  PATHS
+    ${DCMTK_DIR}/dcmjpeg/libsrc
+    ${DCMTK_DIR}/dcmjpeg/libsrc/Release
+    ${DCMTK_DIR}/dcmjpeg/libsrc/Debug
+    ${DCMTK_DIR}/dcmjpeg/Release
+    ${DCMTK_DIR}/dcmjpeg/Debug
+    ${DCMTK_DIR}/lib
+  NO_DEFAULT_PATH
+)
+
+FIND_LIBRARY( DCMTK_ijg16_LIBRARY ijg16
+  PATHS
+    ${DCMTK_DIR}/dcmjpeg/libsrc
+    ${DCMTK_DIR}/dcmjpeg/libsrc/Release
+    ${DCMTK_DIR}/dcmjpeg/libsrc/Debug
+    ${DCMTK_DIR}/dcmjpeg/Release
+    ${DCMTK_DIR}/dcmjpeg/Debug
+    ${DCMTK_DIR}/lib
+  NO_DEFAULT_PATH
+)
+
+FIND_LIBRARY( DCMTK_ijg8_LIBRARY ijg8
+  PATHS
+    ${DCMTK_DIR}/dcmjpeg/libsrc
+    ${DCMTK_DIR}/dcmjpeg/libsrc/Release
+    ${DCMTK_DIR}/dcmjpeg/libsrc/Debug
+    ${DCMTK_DIR}/dcmjpeg/Release
+    ${DCMTK_DIR}/dcmjpeg/Debug
+    ${DCMTK_DIR}/lib
+  NO_DEFAULT_PATH
+)
+
 FIND_PATH( DCMTK_dcmnet_INCLUDE_DIR dimse.h
   PATHS
     ${DCMTK_DIR}/include/dcmnet
@@ -163,6 +216,8 @@ IF( DCMTK_config_INCLUDE_DIR
     AND DCMTK_oflog_LIBRARY
     AND DCMTK_dcmdata_INCLUDE_DIR
     AND DCMTK_dcmdata_LIBRARY
+    AND DCMTK_dcmjpeg_INCLUDE_DIR
+    AND DCMTK_dcmjpeg_LIBRARY
     AND DCMTK_dcmnet_INCLUDE_DIR
     AND DCMTK_dcmnet_LIBRARY    
     AND DCMTK_dcmimgle_INCLUDE_DIR
@@ -185,6 +240,7 @@ IF( DCMTK_config_INCLUDE_DIR
     ${DCMTK_ofstd_INCLUDE_DIR}
     ${DCMTK_oflog_INCLUDE_DIR}    
     ${DCMTK_dcmdata_INCLUDE_DIR}
+    ${DCMTK_dcmjpeg_INCLUDE_DIR}
     ${DCMTK_dcmnet_INCLUDE_DIR}
     ${DCMTK_dcmimgle_INCLUDE_DIR}
   )
@@ -192,7 +248,11 @@ IF( DCMTK_config_INCLUDE_DIR
   SET( DCMTK_LIBRARIES
     ${DCMTK_dcmimgle_LIBRARY}
     ${DCMTK_dcmnet_LIBRARY}    
+    ${DCMTK_dcmjpeg_LIBRARY}
     ${DCMTK_dcmdata_LIBRARY}
+    ${DCMTK_ijg8_LIBRARY}
+    ${DCMTK_ijg12_LIBRARY}
+    ${DCMTK_ijg16_LIBRARY}
     ${DCMTK_oflog_LIBRARY}    
     ${DCMTK_ofstd_LIBRARY}
     ${DCMTK_config_LIBRARY}
@@ -213,17 +273,7 @@ IF( DCMTK_config_INCLUDE_DIR
 #     SET( DCMTK_LIBRARIES ${DCMTK_LIBRARIES} ${DCMTK_wrap_LIBRARY} )
 #   ENDIF()
 
-ENDIF( DCMTK_config_INCLUDE_DIR 
-    AND DCMTK_ofstd_INCLUDE_DIR 
-    AND DCMTK_ofstd_LIBRARY
-    AND DCMTK_oflog_INCLUDE_DIR
-    AND DCMTK_oflog_LIBRARY
-    AND DCMTK_dcmdata_INCLUDE_DIR
-    AND DCMTK_dcmdata_LIBRARY
-    AND DCMTK_dcmnet_INCLUDE_DIR
-    AND DCMTK_dcmnet_LIBRARY    
-    AND DCMTK_dcmimgle_INCLUDE_DIR
-    AND DCMTK_dcmimgle_LIBRARY )
+ENDIF()
 
 FIND_PROGRAM(DCMTK_DCMDUMP_EXECUTABLE dcmdump
   PATHS