瀏覽代碼

Adapted code to latest DCMTK and fixed some DICOM functionality.
Changed DCMTK logger to be in DCMTK namespace. Fixed series retrieve fucntion
to also include Study Instance UID in request. Added documentation. Corrected
selection of correct presentation context.

Michael Onken 14 年之前
父節點
當前提交
9663c4cefc
共有 2 個文件被更改,包括 137 次插入78 次删除
  1. 133 76
      Libs/DICOM/Core/ctkDICOMRetrieve.cpp
  2. 4 2
      Libs/DICOM/Core/ctkDICOMRetrieve.h

+ 133 - 76
Libs/DICOM/Core/ctkDICOMRetrieve.cpp

@@ -38,7 +38,7 @@
 
 
 // DCMTK includes
 // DCMTK includes
 #ifndef WIN32
 #ifndef WIN32
-  #define HAVE_CONFIG_H 
+  #define HAVE_CONFIG_H
 #endif
 #endif
 #include "dcmtk/dcmnet/dimse.h"
 #include "dcmtk/dcmnet/dimse.h"
 #include "dcmtk/dcmnet/diutil.h"
 #include "dcmtk/dcmnet/diutil.h"
@@ -72,7 +72,7 @@ public:
   QString       CallingAETitle;
   QString       CallingAETitle;
   QString       CalledAETitle;
   QString       CalledAETitle;
   QString       Host;
   QString       Host;
-  int           CallingPort;
+  int           CallingPort; // TODO: Not used yet since C-STORE receiver must be run separately so far
   int           CalledPort;
   int           CalledPort;
   DcmSCU        SCU; // TODO: not used yet.
   DcmSCU        SCU; // TODO: not used yet.
   DcmDataset*   parameters;
   DcmDataset*   parameters;
@@ -81,7 +81,9 @@ public:
 
 
   // do the retrieve, handling both series and study retrieves
   // do the retrieve, handling both series and study retrieves
   enum RetrieveType { RetrieveSeries, RetrieveStudy };
   enum RetrieveType { RetrieveSeries, RetrieveStudy };
-  bool retrieve ( QString UID, RetrieveType retriveType );
+  bool retrieve ( const QString& studyInstanceUID,
+                  const QString& seriesInstanceUID,
+                  const RetrieveType rType );
 };
 };
 
 
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------
@@ -103,16 +105,19 @@ ctkDICOMRetrievePrivate::~ctkDICOMRetrievePrivate()
 }
 }
 
 
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------
-bool ctkDICOMRetrievePrivate::retrieve ( QString UID, RetrieveType retriveType ) {
+bool ctkDICOMRetrievePrivate::retrieve ( const QString& studyInstanceUID,
+                                         const QString& seriesInstanceUID,
+                                         const RetrieveType rType )
+{
 
 
   if ( !this->RetrieveDatabase )
   if ( !this->RetrieveDatabase )
     {
     {
-    logger.error ( "Must have RetrieveDatabase for retrieve transaction" );
+    logger.error ( "No RetrieveDatabase for retrieve transaction" );
     return false;
     return false;
     }
     }
 
 
   // Register the JPEG libraries in case we need them
   // Register the JPEG libraries in case we need them
-  //   (registration only happens once, so it's okay to call repeatedly)
+  // (registration only happens once, so it's okay to call repeatedly)
   // register global JPEG decompression codecs
   // register global JPEG decompression codecs
   DJDecoderRegistration::registerCodecs();
   DJDecoderRegistration::registerCodecs();
   // register global JPEG compression codecs
   // register global JPEG compression codecs
@@ -123,125 +128,166 @@ bool ctkDICOMRetrievePrivate::retrieve ( QString UID, RetrieveType retriveType )
   DcmRLEDecoderRegistration::registerCodecs();
   DcmRLEDecoderRegistration::registerCodecs();
 
 
   // Set the DCMTK log level
   // Set the DCMTK log level
-  log4cplus::Logger rootLogger = log4cplus::Logger::getRoot();
-  rootLogger.setLogLevel(log4cplus::DEBUG_LOG_LEVEL);
+  dcmtk::log4cplus::Logger rootLogger = dcmtk::log4cplus::Logger::getRoot();
+  rootLogger.setLogLevel(dcmtk::log4cplus::DEBUG_LOG_LEVEL);
 
 
-  // TODO: use this->SCU instead ?
+  // TODO: use this->SCU instead?
   DcmSCU scu;
   DcmSCU scu;
   scu.setAETitle ( OFString(this->CallingAETitle.toStdString().c_str()) );
   scu.setAETitle ( OFString(this->CallingAETitle.toStdString().c_str()) );
-  scu.setPort ( this->CallingPort );
   scu.setPeerAETitle ( OFString(this->CalledAETitle.toStdString().c_str()) );
   scu.setPeerAETitle ( OFString(this->CalledAETitle.toStdString().c_str()) );
   scu.setPeerHostName ( OFString(this->Host.toStdString().c_str()) );
   scu.setPeerHostName ( OFString(this->Host.toStdString().c_str()) );
   scu.setPeerPort ( this->CalledPort );
   scu.setPeerPort ( this->CalledPort );
-  scu.setMoveDestinationAETitle ( OFString(this->MoveDestinationAETitle.toStdString().c_str()) );
 
 
   logger.info ( "Setting Transfer Syntaxes" );
   logger.info ( "Setting Transfer Syntaxes" );
   OFList<OFString> transferSyntaxes;
   OFList<OFString> transferSyntaxes;
   transferSyntaxes.push_back ( UID_LittleEndianExplicitTransferSyntax );
   transferSyntaxes.push_back ( UID_LittleEndianExplicitTransferSyntax );
   transferSyntaxes.push_back ( UID_BigEndianExplicitTransferSyntax );
   transferSyntaxes.push_back ( UID_BigEndianExplicitTransferSyntax );
   transferSyntaxes.push_back ( UID_LittleEndianImplicitTransferSyntax );
   transferSyntaxes.push_back ( UID_LittleEndianImplicitTransferSyntax );
-  scu.addPresentationContext ( UID_FINDStudyRootQueryRetrieveInformationModel, transferSyntaxes );
   scu.addPresentationContext ( UID_MOVEStudyRootQueryRetrieveInformationModel, transferSyntaxes );
   scu.addPresentationContext ( UID_MOVEStudyRootQueryRetrieveInformationModel, transferSyntaxes );
 
 
-  if ( !scu.initNetwork().good() ) 
+  // Check and initialize networking parameters in DCMTK
+  if ( !scu.initNetwork().good() )
     {
     {
     logger.error ( "Error initializing the network" );
     logger.error ( "Error initializing the network" );
     return false;
     return false;
     }
     }
+
+  // Negotiate (i.e. start the) association
   logger.debug ( "Negotiating Association" );
   logger.debug ( "Negotiating Association" );
+
   if ( !scu.negotiateAssociation().good() )
   if ( !scu.negotiateAssociation().good() )
     {
     {
     logger.error ( "Error negotiating association" );
     logger.error ( "Error negotiating association" );
     return false;;
     return false;;
     }
     }
 
 
+  // Setup query what to be received from the PACS
   logger.debug ( "Setting Parameters" );
   logger.debug ( "Setting Parameters" );
   // Clear the query
   // 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 )
+  parameters->clear();
+  if ( rType == RetrieveSeries )
     {
     {
     this->parameters->putAndInsertString ( DCM_QueryRetrieveLevel, "SERIES" );
     this->parameters->putAndInsertString ( DCM_QueryRetrieveLevel, "SERIES" );
-    this->parameters->putAndInsertString ( DCM_SeriesInstanceUID, UID.toStdString().c_str() );
-    } 
+    this->parameters->putAndInsertString ( DCM_SeriesInstanceUID, seriesInstanceUID.toStdString().c_str() );
+    // Always required to send all highler level unique keys, so add study here (we are in Study Root)
+    this->parameters->putAndInsertString ( DCM_StudyInstanceUID, studyInstanceUID.toStdString().c_str() );  //TODO
+    }
   else
   else
     {
     {
     this->parameters->putAndInsertString ( DCM_QueryRetrieveLevel, "STUDY" );
     this->parameters->putAndInsertString ( DCM_QueryRetrieveLevel, "STUDY" );
-    this->parameters->putAndInsertString ( DCM_StudyInstanceUID, UID.toStdString().c_str() );  
+    this->parameters->putAndInsertString ( DCM_StudyInstanceUID, studyInstanceUID.toStdString().c_str() );
     }
     }
 
 
+  // Issue request
   logger.debug ( "Sending Move Request" );
   logger.debug ( "Sending Move Request" );
-  MOVEResponses *responses = new MOVEResponses();
-  OFCondition status = scu.sendMOVERequest ( 0, this->parameters, responses );
-  if (!status.good())
+  MOVEResponses responses;
+  T_ASC_PresentationContextID presID = scu.findPresentationContextID(UID_MOVEStudyRootQueryRetrieveInformationModel, "" /* don't care about transfer syntax */ );
+  if (presID == 0)
     {
     {
-    logger.error ( "MOVE Request failed: " + QString ( status.text() ) );
+    logger.error ( "MOVE Request failed: No valid Study Root MOVE Presentation Context available" );
+    scu.closeAssociation(DCMSCU_RELEASE_ASSOCIATION);
     return false;
     return false;
     }
     }
+  OFCondition status = scu.sendMOVERequest ( presID, this->MoveDestinationAETitle.toStdString().c_str(), this->parameters, &responses );
+  // Close association, no need to keep it open
+  scu.closeAssociation(DCMSCU_RELEASE_ASSOCIATION);
 
 
-  logger.debug ( "Move Request succeded" );
-
-  logger.debug ( "Making Output Directory" );
-  QDir directory = QDir( RetrieveDatabase->databaseDirectory() );
-
-  if ( responses->begin() == responses->end() )
+  // If we do not receive a single response, something is fishy
+  if ( responses.begin() == responses.end() )
     {
     {
-    logger.error ( "No responses!" );
-    throw std::runtime_error( std::string("No responses!") );
+    logger.error ( "No responses received at all! (at least one empty response always expected)" );
+    throw std::runtime_error( std::string("No responses received from server!") );
     }
     }
 
 
-  // Write the responses out to disk
-  for ( OFListIterator(FINDResponse*) it = responses->begin(); it != responses->end(); it++ )
+  /* The server is permitted to acknowledge every image that was received, or
+   * to send a single move response.
+   * If there is only a single response, this can mean the following:
+   * 1) No images to transfer (Status Success and Number of Completed Subops = 0)
+   * 2) All images transferred (Status Success and Number of Completed Subops > 0)
+   * 3) Error code, i.e. no images transferred
+   * 4) Warning (one or more failures, i.e. some images transferred)
+   */
+  if ( responses.numResults() == 1 )
     {
     {
-    DcmDataset *dataset = (*it)->m_dataset;
-    if ( dataset != NULL )
+    MOVEResponse* rsp = *responses.begin();
+    logger.debug ( "MOVE response receveid with status: " + QString(DU_cmoveStatusString(rsp->m_status)) );
+    if ((rsp->m_status == STATUS_Success) || (rsp->m_status == STATUS_MOVE_Warning_SubOperationsCompleteOneOrMoreFailures))
       {
       {
-      logger.debug ( "Got a valid dataset" );
-      // Save in correct directory
-      E_TransferSyntax output_transfersyntax = dataset->getOriginalXfer();
-      dataset->chooseRepresentation( output_transfersyntax, NULL );
-        
-      if ( !dataset->canWriteXfer( output_transfersyntax ) )
+      if (rsp->m_numberOfCompletedSubops == 0)
         {
         {
-        // Pick EXS_LittleEndianExplicit as our default
-        output_transfersyntax = EXS_LittleEndianExplicit;
+        logger.error ( "No images transferred by PACS!" );
+        throw std::runtime_error( std::string("No images transferred by PACS!") );
         }
         }
-        
-      DcmXfer opt_oxferSyn( output_transfersyntax );
-      if ( !dataset->chooseRepresentation( opt_oxferSyn.getXfer(), NULL ).bad() )
+      }
+    else
+      {
+      logger.error("MOVE request failed, server does report error");
+      QString statusDetail("No details");
+      if (rsp->m_statusDetail != NULL)
         {
         {
-        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() );
-          }
-
-        RetrieveDatabase->insert( dataset, true );
-          
-        delete fileformat;
+         std::ostringstream out;
+        rsp->m_statusDetail->print(out);
+        statusDetail = "Status Detail: " + statusDetail.fromStdString(out.str());
         }
         }
+      statusDetail.prepend("MOVE request failed: ");
+      logger.error(statusDetail);
+      throw std::runtime_error( statusDetail.toStdString() );
       }
       }
     }
     }
-
-
-  delete responses;
-  if ( !scu.dropNetwork().good() ) 
+  // Select the last MOVE response to output meaningful status information
+  OFListIterator(MOVEResponse*) it = responses.begin();
+  Uint32 numResults = responses.numResults();
+  for (Uint32 i = 1; i < numResults; i++)
     {
     {
-    logger.error ( "Error dropping the network" );
-    return false;
+    it++;
     }
     }
+  logger.debug ( "MOVE responses report for study: " + studyInstanceUID +"\n"
+    + QString::number((*it)->m_numberOfCompletedSubops) + " images transferred, and\n"
+    + QString::number((*it)->m_numberOfWarningSubops)   + " images transferred with warning, and\n"
+    + QString::number((*it)->m_numberOfFailedSubops)    + " images transfers failed");
+
+  /* Comment from Michael: The code below does not make sense. Using MOVE you never
+   * receive the image here but only status information; thus, rsp->m_dataset is _not_
+   * an image. I leave it inside since it might be moved to a location which makes more
+   * sense.
+   */
+
+ // for ( OFListIterator(MOVEResponse*) it = responses.begin(); it != responses.end(); it++ )
+ // {
+ //    DcmDataset *dataset = (*it)->m_dataset;
+ //   if ( dataset != NULL )
+ //   {
+ //     logger.debug ( "Got a valid dataset" );
+ //     // 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( dataset );
+ //
+ //       // Follow dcmdjpeg example
+ //       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() );
+ //       }
+ //      // Insert into our local database
+ //       RetrieveDatabase->insert( dataset, true );
+ //     }
+ //   }
+ // }
   return true;
   return true;
 }
 }
 
 
@@ -358,17 +404,28 @@ QSharedPointer<ctkDICOMDatabase> ctkDICOMRetrieve::retrieveDatabase()const
 }
 }
 
 
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------
-bool ctkDICOMRetrieve::retrieveSeries( const QString& seriesInstanceUID )
+bool ctkDICOMRetrieve::retrieveSeries(const QString& studyInstanceUID,
+                                      const QString& seriesInstanceUID )
 {
 {
+  if (studyInstanceUID.isEmpty() || seriesInstanceUID.isEmpty())
+    {
+    logger.error("Cannot receive series: Either Study or Series Instance UID empty.");
+    return false;
+    }
   Q_D(ctkDICOMRetrieve);
   Q_D(ctkDICOMRetrieve);
   logger.info ( "Starting retrieveSeries" );
   logger.info ( "Starting retrieveSeries" );
-  return d->retrieve ( seriesInstanceUID, ctkDICOMRetrievePrivate::RetrieveSeries );
+  return d->retrieve ( studyInstanceUID, seriesInstanceUID, ctkDICOMRetrievePrivate::RetrieveSeries );
 }
 }
 
 
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------
 bool ctkDICOMRetrieve::retrieveStudy( const QString& studyInstanceUID )
 bool ctkDICOMRetrieve::retrieveStudy( const QString& studyInstanceUID )
 {
 {
+  if (studyInstanceUID.isEmpty())
+    {
+    logger.error("Cannot receive series: Either Study or Series Instance UID empty.");
+    return false;
+    }
   Q_D(ctkDICOMRetrieve);
   Q_D(ctkDICOMRetrieve);
   logger.info ( "Starting retrieveStudy" );
   logger.info ( "Starting retrieveStudy" );
-  return d->retrieve ( studyInstanceUID, ctkDICOMRetrievePrivate::RetrieveStudy );
+  return d->retrieve ( studyInstanceUID, "", ctkDICOMRetrievePrivate::RetrieveStudy );
 }
 }

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

@@ -22,7 +22,7 @@
 #define __ctkDICOMRetrieve_h
 #define __ctkDICOMRetrieve_h
 
 
 
 
-// Qt includes 
+// Qt includes
 #include <QObject>
 #include <QObject>
 #include <QDir>
 #include <QDir>
 #include <QSharedPointer>
 #include <QSharedPointer>
@@ -72,7 +72,9 @@ public:
   QSharedPointer<ctkDICOMDatabase> retrieveDatabase()const;
   QSharedPointer<ctkDICOMDatabase> retrieveDatabase()const;
 
 
   // Could be a slot...
   // Could be a slot...
-  bool retrieveSeries( const QString& seriesInstanceUID );
+  bool retrieveSeries( const QString& studyInstanceUID,
+                       const QString& seriesInstanceUID );
+
   bool retrieveStudy( const QString& studyInstanceUID );
   bool retrieveStudy( const QString& studyInstanceUID );
 
 
 protected:
 protected: