Browse Source

Merge branch 'ctkDcmSCU'

Steve Pieper 13 years ago
parent
commit
228c190072

+ 2 - 2
Applications/ctkDICOMDemoSCU/ctkDICOMDemoSCUMain.cpp

@@ -19,7 +19,7 @@
 =========================================================================*/
 
 #include "dcmtk/config/osconfig.h"    /* make sure OS specific configuration is included first */
-#include "dcmtk/dcmnet/scu.h"
+#include "ctkDcmSCU.h"
 
 // STD includes
 #include <cstdlib>
@@ -53,7 +53,7 @@ int main(int argc, char** argv)
   }
     
   // Setup SCU
-  DcmSCU scu;
+  ctkDcmSCU scu;
   scu.setPeerHostName(host);
   scu.setPeerPort(port);
   OFString verificationSOP = UID_VerificationSOPClass;

+ 3 - 3
Applications/ctkDICOMRetrieve/ctkDICOMRetrieveMain.cpp

@@ -81,7 +81,7 @@ int main(int argc, char** argv)
   ctkDICOMRetrieve retrieve;
   retrieve.setCallingAETitle ( CallingAETitle );
   retrieve.setCalledAETitle ( CalledAETitle );
-  retrieve.setCalledPort ( CalledPort );
+  retrieve.setPort ( CalledPort );
   retrieve.setHost ( Host );
   retrieve.setMoveDestinationAETitle ( MoveDestinationAETitle );
 
@@ -94,12 +94,12 @@ int main(int argc, char** argv)
 
   QSharedPointer<ctkDICOMDatabase> dicomDatabase =  QSharedPointer<ctkDICOMDatabase> (new ctkDICOMDatabase);
   dicomDatabase->openDatabase( OutputDirectory.absoluteFilePath(QString("ctkDICOM.sql")) );
-  retrieve.setRetrieveDatabase( dicomDatabase );
+  retrieve.setDatabase( dicomDatabase );
 
   logger.info ( "Starting to retrieve" );
   try
     {
-    retrieve.retrieveStudy ( StudyUID );
+    retrieve.moveStudy ( StudyUID );
     }
   catch (std::exception e)
     {

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

@@ -29,6 +29,11 @@ set(KIT_SRCS
   ctkDICOMRetrieve.h
   ctkDICOMTester.cpp
   ctkDICOMTester.h
+  # enhanced DcmSCU class - to be removed when
+  # corresponding functionality is in a 
+  # DCMKT release - see notes in header file.
+  ctkDcmSCU.cc
+
 )
 
 # Abstract class should not be wrapped !

+ 28 - 12
Libs/DICOM/Core/Testing/Cpp/ctkDICOMRetrieveTest1.cpp

@@ -41,14 +41,14 @@ int ctkDICOMRetrieveTest1( int argc, char * argv [] )
   if (!retrieve.callingAETitle().isEmpty() ||
       !retrieve.calledAETitle().isEmpty() ||
       !retrieve.host().isEmpty() ||
-      retrieve.calledPort() != 0 ||
+      retrieve.port() != 0 ||
       !retrieve.moveDestinationAETitle().isEmpty())
     {
     std::cerr << "ctkDICOMRetrieve::ctkDICOMRetrieve() failed: "
               << qPrintable(retrieve.callingAETitle()) << " "
               << qPrintable(retrieve.calledAETitle()) << " "
               << qPrintable(retrieve.host()) << " "
-              << retrieve.calledPort() << " "
+              << retrieve.port() << " "
               << qPrintable(retrieve.moveDestinationAETitle()) << std::endl;
     return EXIT_FAILURE;
     }
@@ -77,36 +77,52 @@ int ctkDICOMRetrieveTest1( int argc, char * argv [] )
     return EXIT_FAILURE;
     }
 
-  retrieve.setCalledPort(80);
-  if (retrieve.calledPort() != 80)
+  retrieve.setPort(80);
+  if (retrieve.port() != 80)
     {
     std::cerr << "ctkDICOMRetrieve::setCalledPort() failed: "
-              << retrieve.calledPort() << std::endl;
+              << retrieve.port() << std::endl;
     return EXIT_FAILURE;
     }
 
   QSharedPointer<ctkDICOMDatabase> dicomDatabase(new ctkDICOMDatabase);
-  retrieve.setRetrieveDatabase(dicomDatabase);
+  retrieve.setDatabase(dicomDatabase);
 
-  if (retrieve.retrieveDatabase() != dicomDatabase)
+  if (retrieve.database() != dicomDatabase)
     {
-    std::cerr << __LINE__ << ": ctkDICOMRetrieve::setRetrieveDatabase() failed."
+    std::cerr << __LINE__ << ": ctkDICOMRetrieve::setDatabase() failed."
               << std::endl;
     return EXIT_FAILURE;
     }
 
-  bool res = retrieve.retrieveSeries(QString(), QString());
+  bool res = retrieve.moveSeries(QString(), QString());
   if (res)
     {
-    std::cerr << __LINE__ << ": ctkDICOMRetrieve::retrieveSeries() should fail."
+    std::cerr << __LINE__ << ": ctkDICOMRetrieve::moveSeries() should fail."
               << std::endl;
     return EXIT_FAILURE;
     }
 
-  res = retrieve.retrieveStudy(QString());
+  res = retrieve.moveStudy(QString());
   if (res)
     {
-    std::cerr << __LINE__ << ": ctkDICOMRetrieve::retrieveStudy() should fail."
+    std::cerr << __LINE__ << ": ctkDICOMRetrieve::moveStudy() should fail."
+              << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  res = retrieve.getSeries(QString(), QString());
+  if (res)
+    {
+    std::cerr << __LINE__ << ": ctkDICOMRetrieve::getSeries() should fail."
+              << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  res = retrieve.getStudy(QString());
+  if (res)
+    {
+    std::cerr << __LINE__ << ": ctkDICOMRetrieve::getStudy() should fail."
               << std::endl;
     return EXIT_FAILURE;
     }

+ 3 - 3
Libs/DICOM/Core/Testing/Cpp/ctkDICOMRetrieveTest2.cpp

@@ -83,15 +83,15 @@ int ctkDICOMRetrieveTest2( int argc, char * argv [] )
   ctkDICOMRetrieve retrieve;
   retrieve.setCallingAETitle("CTK_AE");
   retrieve.setCalledAETitle("CTK_AE");
-  retrieve.setCalledPort(tester.dcmqrscpPort());
+  retrieve.setPort(tester.dcmqrscpPort());
   retrieve.setHost("localhost");
   retrieve.setMoveDestinationAETitle("CTK_CLIENT_AE");
 
-  retrieve.setRetrieveDatabase(retrieveDatabase);
+  retrieve.setDatabase(retrieveDatabase);
 
   foreach(const QString& study, query.studyInstanceUIDQueried())
     {
-    bool res = retrieve.retrieveStudy(study);
+    bool res = retrieve.moveStudy(study);
     if (!res)
       {
       std::cout << "ctkDICOMRetrieve::retrieveStudy() failed. "

+ 40 - 65
Libs/DICOM/Core/ctkDICOMDatabase.cpp

@@ -521,7 +521,7 @@ void ctkDICOMDatabasePrivate::insert( const ctkDICOMDataset& ctkDataset, const Q
   QString studyInstanceUID(ctkDataset.GetElementAsString(DCM_StudyInstanceUID) );
   QString seriesInstanceUID(ctkDataset.GetElementAsString(DCM_SeriesInstanceUID) );
   QString patientID(ctkDataset.GetElementAsString(DCM_PatientID) );
-  if ( patientsName.isEmpty() || studyInstanceUID.isEmpty() || seriesInstanceUID.isEmpty() || patientID.isEmpty() )
+  if ( patientsName.isEmpty() || studyInstanceUID.isEmpty() || patientID.isEmpty() )
   {
     logger.error("Dataset is missing necessary information!");
     return;
@@ -560,7 +560,7 @@ void ctkDICOMDatabasePrivate::insert( const ctkDICOMDataset& ctkDataset, const Q
   // have to do something else
   // 
   QString filename = filePath;
-  if ( storeFile && !q->isInMemory() )
+  if ( storeFile && !q->isInMemory() && !seriesInstanceUID.isEmpty() )
   {
     // QString studySeriesDirectory = studyInstanceUID + "/" + seriesInstanceUID;
     QString destinationDirectoryName = q->databaseDirectory() + "/dicom/";
@@ -595,15 +595,19 @@ void ctkDICOMDatabasePrivate::insert( const ctkDICOMDataset& ctkDataset, const Q
   }
 
   QSqlQuery checkPatientExistsQuery(Database);
-  //The dbPatientID  is a unique number within the database, generated by the sqlite autoincrement
+  //The dbPatientID  is a unique number within the database, 
+  //generated by the sqlite autoincrement
   //The patientID  is the (non-unique) DICOM patient id
   int dbPatientID = -1;
 
   if ( patientID != "" && patientsName != "" )
-  {
-    //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 != patientID || lastPatientsBirthDate != patientsBirthDate || lastPatientsName != patientsName )
     {
+    //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 != patientID 
+          || lastPatientsBirthDate != patientsBirthDate 
+          || lastPatientsName != patientsName )
+      {
       // Ok, something is different from last insert, let's insert him if he's not
       // already in the db.
       //
@@ -621,7 +625,7 @@ void ctkDICOMDatabasePrivate::insert( const ctkDICOMDataset& ctkDataset, const Q
         dbPatientID = checkPatientExistsQuery.value(checkPatientExistsQuery.record().indexOf("UID")).toInt();
       }
       else
-      {
+        {
         // Insert it
         QSqlQuery insertPatientStatement ( Database );
         insertPatientStatement.prepare ( "INSERT INTO Patients ('UID', 'PatientsName', 'PatientID', 'PatientsBirthDate', 'PatientsBirthTime', 'PatientsSex', 'PatientsAge', 'PatientsComments' ) values ( NULL, ?, ?, ?, ?, ?, ?, ? )" );
@@ -630,20 +634,20 @@ void ctkDICOMDatabasePrivate::insert( const ctkDICOMDataset& ctkDataset, const Q
         insertPatientStatement.bindValue ( 2, patientsBirthDate );
         insertPatientStatement.bindValue ( 3, patientsBirthTime );
         insertPatientStatement.bindValue ( 4, patientsSex );
-        // TODO: shift patient's age to study, since this is not a patient level attribute in images
+        // TODO: shift patient's age to study, 
+        // since this is not a patient level attribute in images
         // insertPatientStatement.bindValue ( 5, patientsAge );
         insertPatientStatement.bindValue ( 6, patientComments );
         loggedExec(insertPatientStatement);
         dbPatientID = insertPatientStatement.lastInsertId().toInt();
         logger.debug ( "New patient inserted: " + QString().setNum ( dbPatientID ) );
-
-      }
+        }
       /// keep this for the next image
       lastPatientUID = dbPatientID;
       lastPatientID = patientID;
       lastPatientsBirthDate = patientsBirthDate;
       lastPatientsName = patientsName;
-    }
+      }
 
     // Patient is in now. Let's continue with the study
 
@@ -706,7 +710,9 @@ void ctkDICOMDatabasePrivate::insert( const ctkDICOMDataset& ctkDataset, const Q
         insertSeriesStatement.bindValue ( 12, static_cast<int>(temporalPosition) );
         if ( !insertSeriesStatement.exec() )
         {
-          logger.error ( "Error executing statament: " + insertSeriesStatement.lastQuery() + " Error: " + insertSeriesStatement.lastError().text() );
+          logger.error ( "Error executing statament: " 
+            + insertSeriesStatement.lastQuery() 
+            + " Error: " + insertSeriesStatement.lastError().text() );
           lastSeriesInstanceUID = "";
         }
         else
@@ -718,13 +724,13 @@ void ctkDICOMDatabasePrivate::insert( const ctkDICOMDataset& ctkDataset, const Q
     }
     // TODO: what to do with imported files
     //
-    if ( !filename.isEmpty() )
-    {
-      QSqlQuery checkImageExistsQuery (Database);
-      checkImageExistsQuery.prepare ( "SELECT * FROM Images WHERE Filename = ?" );
-      checkImageExistsQuery.bindValue ( 0, filename );
-      checkImageExistsQuery.exec();
-      if(!checkImageExistsQuery.next())
+   if ( !filename.isEmpty() && !seriesInstanceUID.isEmpty() )
+   {
+     QSqlQuery checkImageExistsQuery (Database);
+     checkImageExistsQuery.prepare ( "SELECT * FROM Images WHERE Filename = ?" );
+     checkImageExistsQuery.bindValue ( 0, filename );
+     checkImageExistsQuery.exec();
+     if(!checkImageExistsQuery.next())
       {
         QSqlQuery insertImageStatement ( Database );
         insertImageStatement.prepare ( "INSERT INTO Images ( 'SOPInstanceUID', 'Filename', 'SeriesInstanceUID', 'InsertTimestamp' ) VALUES ( ?, ?, ?, ? )" );
@@ -736,59 +742,28 @@ void ctkDICOMDatabasePrivate::insert( const ctkDICOMDataset& ctkDataset, const Q
       }
     }
 
-    /**
-     * old move/copy code from indexer insert
-     *
-
-     QString studySeriesDirectory = studyInstanceUID + "/" + seriesInstanceUID;
-
-    //----------------------------------
-    //Move file to destination directory
-    //----------------------------------
-
-    QString finalFilePath(filePath);
-    if (!destinationDirectoryName.isEmpty())
-    {
-    QFile currentFile( filePath );
-    QDir destinationDir(destinationDirectoryName + "/dicom");
-    finalFilePath = sopInstanceUID;
-    if (createHierarchy)
-    {
-    destinationDir.mkpath(studySeriesDirectory);
-    finalFilePath.prepend( destinationDir.absolutePath() + "/"  + studySeriesDirectory + "/" );
-    }
-    currentFile.copy(finalFilePath);
-    logger.debug( "Copy file from: " + filePath );
-    logger.debug( "Copy file to  : " + finalFilePath );
-    }
-    logger.debug(QString("finalFilePath: ") + finalFilePath);
-
-*/
-
-    if(generateThumbnail){
-      if(thumbnailGenerator)
+    if( generateThumbnail && thumbnailGenerator && !seriesInstanceUID.isEmpty() )
       {
-        QString studySeriesDirectory = studyInstanceUID + "/" + seriesInstanceUID;
-        //Create thumbnail here
-        QString thumbnailPath = q->databaseDirectory() +
-          "/thumbs/" + studyInstanceUID + "/" + seriesInstanceUID + "/" + sopInstanceUID + ".png";
-        //studyInstanceUID + "/" +
-        //seriesInstanceUID + "/" +
-        //sopInstanceUID + ".png";
-        QFileInfo thumbnailInfo(thumbnailPath);
-        if(!(thumbnailInfo.exists() && (thumbnailInfo.lastModified() > QFileInfo(filename).lastModified()))){
-          QDir(q->databaseDirectory() + "/thumbs/").mkpath(studySeriesDirectory);
-          DicomImage dcmImage(QDir::toNativeSeparators(filename).toAscii());
-          thumbnailGenerator->generateThumbnail(&dcmImage, thumbnailPath);
+      QString studySeriesDirectory = studyInstanceUID + "/" + seriesInstanceUID;
+      //Create thumbnail here
+      QString thumbnailPath = q->databaseDirectory() +
+        "/thumbs/" + studyInstanceUID + "/" + seriesInstanceUID 
+        + "/" + sopInstanceUID + ".png";
+      QFileInfo thumbnailInfo(thumbnailPath);
+      if( !(thumbnailInfo.exists() 
+            && (thumbnailInfo.lastModified() > QFileInfo(filename).lastModified())))
+        {
+        QDir(q->databaseDirectory() + "/thumbs/").mkpath(studySeriesDirectory);
+        DicomImage dcmImage(QDir::toNativeSeparators(filename).toAscii());
+        thumbnailGenerator->generateThumbnail(&dcmImage, thumbnailPath);
         }
       }
-    }
 
     if (q->isInMemory())
-    {
+      {
       emit q->databaseChanged();
+      }
     }
-  }
 }
 
 bool ctkDICOMDatabase::fileExistsAndUpToDate(const QString& filePath)

+ 42 - 15
Libs/DICOM/Core/ctkDICOMQuery.cpp

@@ -47,11 +47,39 @@
 #include <dcmtk/ofstd/ofstd.h>        /* for class OFStandard */
 #include <dcmtk/dcmdata/dcddirif.h>   /* for class DicomDirInterface */
 
-#include <dcmtk/dcmnet/scu.h>
+// NOTE: using ctk stand-in class for now - switch back
+// to dcmtk's scu.h when cget support is in a release version
+//#include <dcmtk/dcmnet/scu.h>
+#include <ctkDcmSCU.h>
 
 static ctkLogger logger ( "org.commontk.dicom.DICOMQuery" );
 
 //------------------------------------------------------------------------------
+// A customized implemenation so that Qt signals can be emitted
+// when query results are obtained
+class ctkDICOMQuerySCUPrivate : public ctkDcmSCU
+{
+public:
+  ctkDICOMQuery *query;
+  ctkDICOMQuerySCUPrivate()
+    {
+    this->query = 0;
+    };
+  ~ctkDICOMQuerySCUPrivate() {};
+  virtual OFCondition handleFINDResponse(const T_ASC_PresentationContextID  presID,
+                                         QRResponse *response,
+                                         OFBool &waitForNextResponse)
+    {
+      if (this->query)
+        {
+        logger.debug ( "FIND RESPONSE" );
+        emit this->query->queryResponseHandled("Got a find response!");
+        return this->ctkDcmSCU::handleFINDResponse(presID, response, waitForNextResponse);
+        }
+    };
+};
+
+//------------------------------------------------------------------------------
 class ctkDICOMQueryPrivate
 {
 public:
@@ -66,7 +94,7 @@ public:
   QString                 Host;
   int                     Port;
   QMap<QString,QVariant>  Filters;
-  DcmSCU                  SCU;
+  ctkDICOMQuerySCUPrivate SCU;
   DcmDataset*             Query;
   QStringList             StudyInstanceUIDList;
 };
@@ -101,6 +129,8 @@ ctkDICOMQuery::ctkDICOMQuery(QObject* parentObject)
   : QObject(parentObject)
   , d_ptr(new ctkDICOMQueryPrivate)
 {
+  Q_D(ctkDICOMQuery);
+  d->SCU.query = this; // give the dcmtk level access to this for emitting signals
 }
 
 //------------------------------------------------------------------------------
@@ -245,7 +275,7 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database )
 
   // Clear the query
   d->Query->clear();
- 
+
   // Insert all keys that we like to receive values for
   d->Query->insertEmptyElement ( DCM_PatientID );
   d->Query->insertEmptyElement ( DCM_PatientName );
@@ -265,7 +295,7 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database )
 
   d->Query->putAndInsertString ( DCM_QueryRetrieveLevel, "STUDY" );
 
-  /* Now, for all keys that the user provided for filtering on STUDY level, 
+  /* Now, for all keys that the user provided for filtering on STUDY level,
    * overwrite empty keys with value. For now, only Patient's Name, Patient ID,
    * Study Description, Modalities in Study, and Study Date are used.
    */
@@ -316,15 +346,15 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database )
 
   if ( d->Filters.keys().contains("StartDate") && d->Filters.keys().contains("EndDate") )
     {
-    QString dateRange = d->Filters["StartDate"].toString() + 
-                        QString("-") + 
+    QString dateRange = d->Filters["StartDate"].toString() +
+                        QString("-") +
                         d->Filters["EndDate"].toString();
     d->Query->putAndInsertString ( DCM_StudyDate, dateRange.toAscii().data() );
     logger.debug("Query on study date " + dateRange);
     }
   emit progress(30);
 
-  FINDResponses *responses = new FINDResponses();
+  OFList<QRResponse *> responses;
 
   Uint16 presentationContext = 0;
   // Check for any accepted presentation context for FIND in study root (dont care about transfer syntax)
@@ -341,21 +371,20 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database )
     }
   emit progress(40);
 
-  OFCondition status = d->SCU.sendFINDRequest ( presentationContext, d->Query, responses );
+  OFCondition status = d->SCU.sendFINDRequest ( presentationContext, d->Query, &responses );
   if ( !status.good() )
     {
     logger.error ( "Find failed" );
     emit progress("Find failed");
     d->SCU.closeAssociation ( DCMSCU_RELEASE_ASSOCIATION );
     emit progress(100);
-    delete responses;
     return false;
     }
   logger.debug ( "Find succeded");
   emit progress("Find succeded");
   emit progress(50);
 
-  for ( OFListIterator(FINDResponse*) it = responses->begin(); it != responses->end(); it++ )
+  for ( OFIterator<QRResponse*> it = responses.begin(); it != responses.end(); it++ )
     {
     DcmDataset *dataset = (*it)->m_dataset;
     if ( dataset != NULL ) // the last response is always empty
@@ -366,7 +395,6 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database )
       d->addStudyInstanceUID ( StudyInstanceUID.c_str() );
       }
     }
-  delete responses;
 
   /* Only ask for series attributes now. This requires kicking out the rest of former query. */
   d->Query->clear();
@@ -392,11 +420,11 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database )
     emit progress(50 + (progressRatio * i++));
 
     d->Query->putAndInsertString ( DCM_StudyInstanceUID, StudyInstanceUID.toStdString().c_str() );
-    responses = new FINDResponses();
-    status = d->SCU.sendFINDRequest ( presentationContext, d->Query, responses );
+    OFList<QRResponse *> responses;
+    status = d->SCU.sendFINDRequest ( presentationContext, d->Query, &responses );
     if ( status.good() )
       {
-      for ( OFListIterator(FINDResponse*) it = responses->begin(); it != responses->end(); it++ )
+      for ( OFIterator<QRResponse*> it = responses.begin(); it != responses.end(); it++ )
         {
         DcmDataset *dataset = (*it)->m_dataset;
         if ( dataset != NULL )
@@ -413,7 +441,6 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database )
       emit progress(QString("Find on Series level failed for Study: ") + StudyInstanceUID);
       }
     emit progress(50 + (progressRatio * i++));
-    delete responses;
     }
   d->SCU.closeAssociation ( DCMSCU_RELEASE_ASSOCIATION );
   emit progress(100);

+ 6 - 0
Libs/DICOM/Core/ctkDICOMQuery.h

@@ -96,6 +96,10 @@ Q_SIGNALS:
   /// Signal is emitted inside the query() function. It sends the different step
   /// the function is at.
   void progress(const QString& message);
+  /// Signal is emitted from the private SCU class when dicom query responses
+  /// arrive
+  void queryResponseHandled( const QString message );
+
 
 protected:
   QScopedPointer<ctkDICOMQueryPrivate> d_ptr;
@@ -103,6 +107,8 @@ protected:
 private:
   Q_DECLARE_PRIVATE(ctkDICOMQuery);
   Q_DISABLE_COPY(ctkDICOMQuery);
+
+  friend class ctkDICOMQuerySCUPrivate;  // for access to queryResponseHandled
 };
 
 #endif

+ 347 - 135
Libs/DICOM/Core/ctkDICOMRetrieve.cpp

@@ -21,16 +21,6 @@
 #include <stdexcept>
 
 // Qt includes
-#include <QSqlQuery>
-#include <QSqlRecord>
-#include <QVariant>
-#include <QDate>
-#include <QStringList>
-#include <QSet>
-#include <QFile>
-#include <QDirIterator>
-#include <QFileInfo>
-#include <QDebug>
 
 // ctkDICOMCore includes
 #include "ctkDICOMRetrieve.h"
@@ -54,28 +44,109 @@
 #include <dcmtk/dcmdata/dcrledrg.h>  /* for DcmRLEDecoderRegistration */
 #include <dcmtk/dcmdata/dcrleerg.h>  /* for DcmRLEEncoderRegistration */
 
-#include <dcmtk/dcmnet/scu.h>
+// NOTE: using ctk stand-in class for now - switch back
+// to dcmtk's scu.h when cget support is in a release version
+//#include <dcmtk/dcmnet/scu.h>
+#include <ctkDcmSCU.h>
 
 #include "dcmtk/oflog/oflog.h"
 
 static ctkLogger logger("org.commontk.dicom.DICOMRetrieve");
 
 //------------------------------------------------------------------------------
+// A customized local implemenation of the DcmSCU so that Qt signals can be emitted
+// when retrieve results are obtained
+class ctkDICOMRetrieveSCUPrivate : public ctkDcmSCU
+{
+public:
+  ctkDICOMRetrieve *retrieve;
+  ctkDICOMRetrieveSCUPrivate()
+    {
+    this->retrieve = 0;
+    };
+  ~ctkDICOMRetrieveSCUPrivate() {};
+
+  // called when a move reponse comes in: indicates that the
+  // move request is being handled by the remote server.
+  virtual OFCondition handleMOVEResponse(const T_ASC_PresentationContextID  presID,
+                                         RetrieveResponse *response,
+                                         OFBool &waitForNextResponse)
+    {
+      if (this->retrieve)
+        {
+        emit this->retrieve->moveResponseHandled("Got one!");
+        return this->ctkDcmSCU::handleMOVEResponse(
+                        presID, response, waitForNextResponse);
+        }
+      return false;
+    };
+
+  // called when a data set is coming in from a server in
+  // response to a CGET
+  virtual OFCondition handleSTORERequest(const T_ASC_PresentationContextID presID,
+                                         DcmDataset *incomingObject,
+                                         OFBool& continueCGETSession,
+                                         Uint16& cStoreReturnStatus)
+    {
+      if (this->retrieve)
+        {
+        emit this->retrieve->storeRequested("Got a store request!");
+        if (this->retrieve && this->retrieve->database())
+          {
+          this->retrieve->database()->insert(incomingObject);
+          return ECC_Normal;
+          }
+        else
+          {
+          return this->ctkDcmSCU::handleSTORERequest(
+                          presID, incomingObject, continueCGETSession, cStoreReturnStatus);
+          }
+        }
+      return false;
+    };
+
+  // called when status information from remote server
+  // comes in from CGET
+  virtual OFCondition handleCGETResponse(const T_ASC_PresentationContextID presID,
+                                         RetrieveResponse* response,
+                                         OFBool& continueCGETSession)
+    {
+      if (this->retrieve)
+        {
+        emit this->retrieve->retrieveStatusChanged("Got a cget response!");
+        return this->ctkDcmSCU::handleCGETResponse(presID, response, continueCGETSession);
+        }
+      return false;
+    };
+};
+
+
+//------------------------------------------------------------------------------
 class ctkDICOMRetrievePrivate
 {
 public:
   ctkDICOMRetrievePrivate();
   ~ctkDICOMRetrievePrivate();
+  /// Keep the currently negotiated connection to the 
+  /// peer host open unless the connection parameters change
   bool          KeepAssociationOpen;
   bool          ConnectionParamsChanged;
-  QSharedPointer<ctkDICOMDatabase> RetrieveDatabase;
-  DcmSCU        scu;
+  bool          LastRetrieveType;
+  QSharedPointer<ctkDICOMDatabase> Database;
+  ctkDICOMRetrieveSCUPrivate        SCU;
   QString MoveDestinationAETitle;
   // do the retrieve, handling both series and study retrieves
-  enum RetrieveType { RetrieveSeries, RetrieveStudy };
-  bool retrieve ( const QString& studyInstanceUID,
+  enum RetrieveType { RetrieveNone, RetrieveSeries, RetrieveStudy };
+  bool initializeSCU(const QString& studyInstanceUID,
                   const QString& seriesInstanceUID,
-                  const RetrieveType rType );
+                  const RetrieveType retrieveType,
+                  DcmDataset *retrieveParameters);
+  bool move ( const QString& studyInstanceUID,
+                  const QString& seriesInstanceUID,
+                  const RetrieveType retrieveType );
+  bool get ( const QString& studyInstanceUID,
+                  const QString& seriesInstanceUID,
+                  const RetrieveType retrieveType );
 };
 
 //------------------------------------------------------------------------------
@@ -84,57 +155,68 @@ public:
 //------------------------------------------------------------------------------
 ctkDICOMRetrievePrivate::ctkDICOMRetrievePrivate()
 {
-  this->RetrieveDatabase = QSharedPointer<ctkDICOMDatabase> (0);
-  KeepAssociationOpen = true;
-  ConnectionParamsChanged = false;
+  this->Database = QSharedPointer<ctkDICOMDatabase> (0);
+  this->KeepAssociationOpen = true;
+  this->ConnectionParamsChanged = false;
+  this->LastRetrieveType = RetrieveNone;
+
+  // 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();
+
   logger.info ( "Setting Transfer Syntaxes" );
   OFList<OFString> transferSyntaxes;
   transferSyntaxes.push_back ( UID_LittleEndianExplicitTransferSyntax );
   transferSyntaxes.push_back ( UID_BigEndianExplicitTransferSyntax );
   transferSyntaxes.push_back ( UID_LittleEndianImplicitTransferSyntax );
-  scu.addPresentationContext ( UID_MOVEStudyRootQueryRetrieveInformationModel, transferSyntaxes );
+  this->SCU.addPresentationContext ( 
+      UID_MOVEStudyRootQueryRetrieveInformationModel, transferSyntaxes );
+  this->SCU.addPresentationContext ( 
+      UID_GETStudyRootQueryRetrieveInformationModel, transferSyntaxes );
+
+  for (Uint16 i = 0; i < numberOfDcmLongSCUStorageSOPClassUIDs; i++)
+    {
+    this->SCU.addPresentationContext(dcmLongSCUStorageSOPClassUIDs[i], 
+        transferSyntaxes, ASC_SC_ROLE_SCP);
+    }
 }
 
 //------------------------------------------------------------------------------
 ctkDICOMRetrievePrivate::~ctkDICOMRetrievePrivate()
 {
   // At least now be kind to the server and release association
-  scu.closeAssociation(DCMSCU_RELEASE_ASSOCIATION);
+  this->SCU.closeAssociation(DCMSCU_RELEASE_ASSOCIATION);
 }
 
 //------------------------------------------------------------------------------
-bool ctkDICOMRetrievePrivate::retrieve ( const QString& studyInstanceUID,
+bool ctkDICOMRetrievePrivate::initializeSCU( const QString& studyInstanceUID,
                                          const QString& seriesInstanceUID,
-                                         const RetrieveType rType )
+                                         const RetrieveType retrieveType,
+                                         DcmDataset *retrieveParameters)
 {
-
-  if ( !this->RetrieveDatabase )
+  if ( !this->Database )
     {
-    logger.error ( "No RetrieveDatabase for retrieve transaction" );
+    logger.error ( "No Database for retrieve transaction" );
     return false;
     }
 
-  // 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();
-
   // If we like to query another server than before, be sure to disconnect first
-  if (scu.isConnected() && ConnectionParamsChanged)
+  if (this->SCU.isConnected() && this->ConnectionParamsChanged)
   {
-    scu.closeAssociation(DCMSCU_RELEASE_ASSOCIATION);
+    this->SCU.closeAssociation(DCMSCU_RELEASE_ASSOCIATION);
   }
   // Connect to server if not already connected
-  if (!scu.isConnected())
+  if (!this->SCU.isConnected())
     {
     // Check and initialize networking parameters in DCMTK
-    if ( !scu.initNetwork().good() )
+    if ( !this->SCU.initNetwork().good() )
       {
       logger.error ( "Error initializing the network" );
       return false;
@@ -142,48 +224,74 @@ bool ctkDICOMRetrievePrivate::retrieve ( const QString& studyInstanceUID,
     // Negotiate (i.e. start the) association
     logger.debug ( "Negotiating Association" );
 
-    if ( !scu.negotiateAssociation().good() )
+    if ( !this->SCU.negotiateAssociation().good() )
       {
       logger.error ( "Error negotiating association" );
       return false;;
       }
     }
 
-  ConnectionParamsChanged = false;
+  this->ConnectionParamsChanged = false;
   // Setup query about what to be received from the PACS
   logger.debug ( "Setting Retrieve Parameters" );
-  DcmDataset *retrieveParameters = new DcmDataset();
-  if ( rType == RetrieveSeries )
+  if ( retrieveType == RetrieveSeries )
     {
     retrieveParameters->putAndInsertString ( DCM_QueryRetrieveLevel, "SERIES" );
-    retrieveParameters->putAndInsertString ( DCM_SeriesInstanceUID, seriesInstanceUID.toStdString().c_str() );
+    retrieveParameters->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)
-    retrieveParameters->putAndInsertString ( DCM_StudyInstanceUID, studyInstanceUID.toStdString().c_str() );  //TODO
+    retrieveParameters->putAndInsertString ( DCM_StudyInstanceUID, 
+                                                studyInstanceUID.toStdString().c_str() );  //TODO
     }
   else
     {
     retrieveParameters->putAndInsertString ( DCM_QueryRetrieveLevel, "STUDY" );
-    retrieveParameters->putAndInsertString ( DCM_StudyInstanceUID, studyInstanceUID.toStdString().c_str() );
+    retrieveParameters->putAndInsertString ( DCM_StudyInstanceUID, 
+                                                studyInstanceUID.toStdString().c_str() );
     }
+  return true;
+}
+
+//------------------------------------------------------------------------------
+bool ctkDICOMRetrievePrivate::move ( const QString& studyInstanceUID,
+                                         const QString& seriesInstanceUID,
+                                         const RetrieveType retrieveType )
+{
+
+  DcmDataset *retrieveParameters = new DcmDataset();
+  if (! this->initializeSCU(studyInstanceUID, seriesInstanceUID, retrieveType, retrieveParameters) )
+    {
+    delete retrieveParameters;
+    return false;
+    }
+
 
   // Issue request
   logger.debug ( "Sending Move Request" );
-  MOVEResponses responses;
-  T_ASC_PresentationContextID presID = scu.findPresentationContextID(UID_MOVEStudyRootQueryRetrieveInformationModel, "" /* don't care about transfer syntax */ );
+  OFList<RetrieveResponse*> responses;
+  T_ASC_PresentationContextID presID = this->SCU.findPresentationContextID(
+                                          UID_MOVEStudyRootQueryRetrieveInformationModel, 
+                                          "" /* don't care about transfer syntax */ );
   if (presID == 0)
     {
     logger.error ( "MOVE Request failed: No valid Study Root MOVE Presentation Context available" );
-    if (!KeepAssociationOpen)
+    if (!this->KeepAssociationOpen)
       {
-      scu.closeAssociation(DCMSCU_RELEASE_ASSOCIATION);
+      this->SCU.closeAssociation(DCMSCU_RELEASE_ASSOCIATION);
       }
+    delete retrieveParameters;
     return false;
     }
-  OFCondition status = scu.sendMOVERequest ( presID, this->MoveDestinationAETitle.toStdString().c_str(), retrieveParameters, &responses );
+
+  // do the actual move request
+  OFCondition status = this->SCU.sendMOVERequest ( 
+                          presID, this->MoveDestinationAETitle.toStdString().c_str(), 
+                          retrieveParameters, &responses );
+
   // Close association if we do not want to explicitly keep it open
-  if (!KeepAssociationOpen)
+  if (!this->KeepAssociationOpen)
     {
-    scu.closeAssociation(DCMSCU_RELEASE_ASSOCIATION);
+    this->SCU.closeAssociation(DCMSCU_RELEASE_ASSOCIATION);
     }
   // Free some (little) memory
   delete retrieveParameters;
@@ -192,7 +300,8 @@ bool ctkDICOMRetrievePrivate::retrieve ( const QString& studyInstanceUID,
   if ( responses.begin() == responses.end() )
     {
     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!") );
+    //throw std::runtime_error( std::string("No responses received from server!") );
+    return false;
     }
 
   /* The server is permitted to acknowledge every image that was received, or
@@ -203,16 +312,20 @@ bool ctkDICOMRetrievePrivate::retrieve ( const QString& studyInstanceUID,
    * 3) Error code, i.e. no images transferred
    * 4) Warning (one or more failures, i.e. some images transferred)
    */
-  if ( responses.numResults() == 1 )
+  if ( responses.size() == 1 )
     {
-    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))
+    RetrieveResponse* 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))
       {
       if (rsp->m_numberOfCompletedSubops == 0)
         {
         logger.error ( "No images transferred by PACS!" );
-        throw std::runtime_error( std::string("No images transferred by PACS!") );
+        //throw std::runtime_error( std::string("No images transferred by PACS!") );
+        return false;
         }
       }
     else
@@ -227,63 +340,133 @@ bool ctkDICOMRetrievePrivate::retrieve ( const QString& studyInstanceUID,
         }
       statusDetail.prepend("MOVE request failed: ");
       logger.error(statusDetail);
-      throw std::runtime_error( statusDetail.toStdString() );
+      //throw std::runtime_error( statusDetail.toStdString() );
+      return false;
       }
     }
-  // Select the last MOVE response to output meaningful status information
-  OFListIterator(MOVEResponse*) it = responses.begin();
-  Uint32 numResults = responses.numResults();
+    // Select the last MOVE response to output meaningful status information
+    OFIterator<RetrieveResponse*> it = responses.begin();
+  Uint32 numResults = responses.size();
   for (Uint32 i = 1; i < numResults; i++)
     {
     it++;
     }
   logger.debug ( "MOVE responses report for study: " + studyInstanceUID +"\n"
-    + QString::number(static_cast<unsigned int>((*it)->m_numberOfCompletedSubops)) + " images transferred, and\n"
-    + QString::number(static_cast<unsigned int>((*it)->m_numberOfWarningSubops))   + " images transferred with warning, and\n"
-    + QString::number(static_cast<unsigned int>((*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.
+    + QString::number(static_cast<unsigned int>((*it)->m_numberOfCompletedSubops))
+        + " images transferred, and\n"
+    + QString::number(static_cast<unsigned int>((*it)->m_numberOfWarningSubops))
+        + " images transferred with warning, and\n"
+    + QString::number(static_cast<unsigned int>((*it)->m_numberOfFailedSubops))
+        + " images transfers failed");
+
+  return true;
+}
+
+//------------------------------------------------------------------------------
+bool ctkDICOMRetrievePrivate::get ( const QString& studyInstanceUID,
+                                         const QString& seriesInstanceUID,
+                                         const RetrieveType retrieveType )
+{
+  DcmDataset *retrieveParameters = new DcmDataset();
+  if (! this->initializeSCU(studyInstanceUID, seriesInstanceUID, retrieveType, retrieveParameters) )
+    {
+    delete retrieveParameters;
+    return false;
+    }
+
+  // Issue request
+  logger.debug ( "Sending Get Request" );
+  OFList<RetrieveResponse*> responses;
+  T_ASC_PresentationContextID presID = this->SCU.findPresentationContextID(
+                                          UID_GETStudyRootQueryRetrieveInformationModel, 
+                                          "" /* don't care about transfer syntax */ );
+  if (presID == 0)
+    {
+    logger.error ( "GET Request failed: No valid Study Root GET Presentation Context available" );
+    if (!this->KeepAssociationOpen)
+      {
+      this->SCU.closeAssociation(DCMSCU_RELEASE_ASSOCIATION);
+      }
+    delete retrieveParameters;
+    return false;
+    }
+
+
+  // do the actual move request
+  OFCondition status = this->SCU.sendCGETRequest ( 
+                          presID, retrieveParameters, &responses );
+
+  // Close association if we do not want to explicitly keep it open
+  if (!this->KeepAssociationOpen)
+    {
+    this->SCU.closeAssociation(DCMSCU_RELEASE_ASSOCIATION);
+    }
+  // Free some (little) memory
+  delete retrieveParameters;
+
+  // If we do not receive a single response, something is fishy
+  if ( responses.begin() == responses.end() )
+    {
+    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!") );
+    return false;
+    }
+
+  /* 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.size() == 1 )
+    {
+    RetrieveResponse* rsp = *responses.begin();
+    logger.debug ( "GET response receveid with status: " + 
+                      QString(DU_cmoveStatusString(rsp->m_status)) );
+
+    if ( (rsp->m_status == STATUS_Success) 
+            || (rsp->m_status == STATUS_GET_Warning_SubOperationsCompleteOneOrMoreFailures))
+      {
+      if (rsp->m_numberOfCompletedSubops == 0)
+        {
+        logger.error ( "No images transferred by PACS!" );
+        //throw std::runtime_error( std::string("No images transferred by PACS!") );
+        return false;
+        }
+      }
+    else
+      {
+      logger.error("GET request failed, server does report error");
+      QString statusDetail("No details");
+      if (rsp->m_statusDetail != NULL)
+        {
+         std::ostringstream out;
+        rsp->m_statusDetail->print(out);
+        statusDetail = "Status Detail: " + statusDetail.fromStdString(out.str());
+        }
+      statusDetail.prepend("GET request failed: ");
+      logger.error(statusDetail);
+      //throw std::runtime_error( statusDetail.toStdString() );
+      return false;
+      }
+    }
+    // Select the last GET response to output meaningful status information
+    OFIterator<RetrieveResponse*> it = responses.begin();
+  Uint32 numResults = responses.size();
+  for (Uint32 i = 1; i < numResults; i++)
+    {
+    it++;
+    }
+  logger.debug ( "GET responses report for study: " + studyInstanceUID +"\n"
+    + QString::number(static_cast<unsigned int>((*it)->m_numberOfCompletedSubops))
+        + " images transferred, and\n"
+    + QString::number(static_cast<unsigned int>((*it)->m_numberOfWarningSubops))
+        + " images transferred with warning, and\n"
+    + QString::number(static_cast<unsigned int>((*it)->m_numberOfFailedSubops))
+        + " images transfers failed");
 
- // 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;
 }
 
@@ -294,6 +477,8 @@ bool ctkDICOMRetrievePrivate::retrieve ( const QString& studyInstanceUID,
 ctkDICOMRetrieve::ctkDICOMRetrieve()
    : d_ptr(new ctkDICOMRetrievePrivate)
 {
+  Q_D(ctkDICOMRetrieve);
+  d->SCU.retrieve = this; // give the dcmtk level access to this for emitting signals
 }
 
 //------------------------------------------------------------------------------
@@ -306,9 +491,9 @@ ctkDICOMRetrieve::~ctkDICOMRetrieve()
 void ctkDICOMRetrieve::setCallingAETitle( const QString& callingAETitle )
 {
   Q_D(ctkDICOMRetrieve);
-  if (strcmp(callingAETitle.toStdString().c_str(), d->scu.getAETitle().c_str()))
+  if (strcmp(callingAETitle.toStdString().c_str(), d->SCU.getAETitle().c_str()))
   {
-    d->scu.setAETitle(callingAETitle.toStdString().c_str());
+    d->SCU.setAETitle(callingAETitle.toStdString().c_str());
     d->ConnectionParamsChanged = true;
   }
 }
@@ -317,16 +502,16 @@ void ctkDICOMRetrieve::setCallingAETitle( const QString& callingAETitle )
 QString ctkDICOMRetrieve::callingAETitle() const
 {
   Q_D(const ctkDICOMRetrieve);
-  return d->scu.getAETitle().c_str();
+  return d->SCU.getAETitle().c_str();
 }
 
 //------------------------------------------------------------------------------
 void ctkDICOMRetrieve::setCalledAETitle( const QString& calledAETitle )
 {
   Q_D(ctkDICOMRetrieve);
-  if (strcmp(calledAETitle.toStdString().c_str(),d->scu.getPeerAETitle().c_str()))
+  if (strcmp(calledAETitle.toStdString().c_str(),d->SCU.getPeerAETitle().c_str()))
   {
-    d->scu.setPeerAETitle(calledAETitle.toStdString().c_str());
+    d->SCU.setPeerAETitle(calledAETitle.toStdString().c_str());
     d->ConnectionParamsChanged = true;
   }
 }
@@ -335,16 +520,16 @@ void ctkDICOMRetrieve::setCalledAETitle( const QString& calledAETitle )
 QString ctkDICOMRetrieve::calledAETitle()const
 {
   Q_D(const ctkDICOMRetrieve);
-  return d->scu.getPeerAETitle().c_str();
+  return d->SCU.getPeerAETitle().c_str();
 }
 
 //------------------------------------------------------------------------------
 void ctkDICOMRetrieve::setHost( const QString& host )
 {
   Q_D(ctkDICOMRetrieve);
-  if (strcmp(host.toStdString().c_str(), d->scu.getPeerHostName().c_str()))
+  if (strcmp(host.toStdString().c_str(), d->SCU.getPeerHostName().c_str()))
   {
-    d->scu.setPeerHostName(host.toStdString().c_str());
+    d->SCU.setPeerHostName(host.toStdString().c_str());
     d->ConnectionParamsChanged = true;
   }
 }
@@ -353,25 +538,25 @@ void ctkDICOMRetrieve::setHost( const QString& host )
 QString ctkDICOMRetrieve::host()const
 {
   Q_D(const ctkDICOMRetrieve);
-  return d->scu.getPeerHostName().c_str();
+  return d->SCU.getPeerHostName().c_str();
 }
 
 //------------------------------------------------------------------------------
-void ctkDICOMRetrieve::setCalledPort( int port )
+void ctkDICOMRetrieve::setPort( int port )
 {
   Q_D(ctkDICOMRetrieve);
-  if (d->scu.getPeerPort() != port)
+  if (d->SCU.getPeerPort() != port)
   {
-    d->scu.setPeerPort(port);
+    d->SCU.setPeerPort(port);
     d->ConnectionParamsChanged = true;
   }
 }
 
 //------------------------------------------------------------------------------
-int ctkDICOMRetrieve::calledPort()const
+int ctkDICOMRetrieve::port()const
 {
   Q_D(const ctkDICOMRetrieve);
-  return d->scu.getPeerPort();
+  return d->SCU.getPeerPort();
 }
 
 //------------------------------------------------------------------------------
@@ -392,18 +577,17 @@ QString ctkDICOMRetrieve::moveDestinationAETitle()const
 }
 
 //------------------------------------------------------------------------------
-void ctkDICOMRetrieve::setRetrieveDatabase(QSharedPointer<ctkDICOMDatabase> dicomDatabase)
+void ctkDICOMRetrieve::setDatabase(QSharedPointer<ctkDICOMDatabase> dicomDatabase)
 {
   Q_D(ctkDICOMRetrieve);
-  d->RetrieveDatabase = dicomDatabase;
-  // (server parameters do not have to be changed)
+  d->Database = dicomDatabase;
 }
 
 //------------------------------------------------------------------------------
-QSharedPointer<ctkDICOMDatabase> ctkDICOMRetrieve::retrieveDatabase()const
+QSharedPointer<ctkDICOMDatabase> ctkDICOMRetrieve::database()const
 {
   Q_D(const ctkDICOMRetrieve);
-  return d->RetrieveDatabase;
+  return d->Database;
 }
 
 //------------------------------------------------------------------------------
@@ -421,7 +605,33 @@ bool ctkDICOMRetrieve::keepAssociationOpen()
 }
 
 //------------------------------------------------------------------------------
-bool ctkDICOMRetrieve::retrieveSeries(const QString& studyInstanceUID,
+bool ctkDICOMRetrieve::moveStudy(const QString& studyInstanceUID)
+{
+  if (studyInstanceUID.isEmpty())
+    {
+    logger.error("Cannot receive series: Study Instance UID empty.");
+    return false;
+    }
+  Q_D(ctkDICOMRetrieve);
+  logger.info ( "Starting moveStudy" );
+  return d->move ( studyInstanceUID, "", ctkDICOMRetrievePrivate::RetrieveStudy );
+}
+
+//------------------------------------------------------------------------------
+bool ctkDICOMRetrieve::getStudy(const QString& studyInstanceUID)
+{
+  if (studyInstanceUID.isEmpty())
+    {
+    logger.error("Cannot receive series: Study Instance UID empty.");
+    return false;
+    }
+  Q_D(ctkDICOMRetrieve);
+  logger.info ( "Starting getStudy" );
+  return d->get ( studyInstanceUID, "", ctkDICOMRetrievePrivate::RetrieveStudy );
+}
+
+//------------------------------------------------------------------------------
+bool ctkDICOMRetrieve::moveSeries(const QString& studyInstanceUID,
                                       const QString& seriesInstanceUID)
 {
   if (studyInstanceUID.isEmpty() || seriesInstanceUID.isEmpty())
@@ -430,19 +640,21 @@ bool ctkDICOMRetrieve::retrieveSeries(const QString& studyInstanceUID,
     return false;
     }
   Q_D(ctkDICOMRetrieve);
-  logger.info ( "Starting retrieveSeries" );
-  return d->retrieve ( studyInstanceUID, seriesInstanceUID, ctkDICOMRetrievePrivate::RetrieveSeries );
+  logger.info ( "Starting moveSeries" );
+  return d->move ( studyInstanceUID, seriesInstanceUID, ctkDICOMRetrievePrivate::RetrieveSeries );
 }
 
 //------------------------------------------------------------------------------
-bool ctkDICOMRetrieve::retrieveStudy( const QString& studyInstanceUID )
+bool ctkDICOMRetrieve::getSeries(const QString& studyInstanceUID,
+                                      const QString& seriesInstanceUID)
 {
-  if (studyInstanceUID.isEmpty())
+  if (studyInstanceUID.isEmpty() || seriesInstanceUID.isEmpty())
     {
     logger.error("Cannot receive series: Either Study or Series Instance UID empty.");
     return false;
     }
   Q_D(ctkDICOMRetrieve);
-  logger.info ( "Starting retrieveStudy" );
-  return d->retrieve ( studyInstanceUID, "", ctkDICOMRetrievePrivate::RetrieveStudy );
+  logger.info ( "Starting getSeries" );
+  return d->get ( studyInstanceUID, seriesInstanceUID, ctkDICOMRetrievePrivate::RetrieveSeries );
 }
+

+ 42 - 19
Libs/DICOM/Core/ctkDICOMRetrieve.h

@@ -39,52 +39,75 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMRetrieve : public QObject
   Q_PROPERTY(QString callingAETitle READ callingAETitle WRITE setCallingAETitle);
   Q_PROPERTY(QString calledAETitle READ calledAETitle WRITE setCallingAETitle);
   Q_PROPERTY(QString host READ host WRITE setHost);
-  Q_PROPERTY(int calledPort READ calledPort WRITE setCalledPort);
+  Q_PROPERTY(int port READ port WRITE setPort);
   Q_PROPERTY(QString moveDestinationAETitle READ moveDestinationAETitle WRITE setMoveDestinationAETitle)
+  Q_PROPERTY(bool keepAssociationOpen READ keepAssociationOpen WRITE setKeepAssociationOpen)
 
 public:
   explicit ctkDICOMRetrieve();
   virtual ~ctkDICOMRetrieve();
 
   /// Set methods for connectivity
-  /// CTK_AE
+  /// CTK_AE - the AE string by which the peer host might 
+  /// recognize your request
   void setCallingAETitle( const QString& callingAETitle );
   QString callingAETitle() const;
-  /// CTK_AE
+  /// CTK_AE - the AE of the serice of peer host that you are calling
+  /// which tells the host what you are requesting
   void setCalledAETitle( const QString& calledAETitle );
   QString calledAETitle() const;
-  /// localhost
+  /// peer hostname being connected to
   void setHost( const QString& host );
   QString host() const;
-  /// [0, 65365] 11112
-  void setCalledPort( int port );
-  int calledPort() const;
-  /// Typically CTK_CLIENT_AE
+  /// [0, 65365] port on peer host - e.g. 11112
+  void setPort( int port );
+  int port() const;
+  /// Typically CTK_STORE or similar - needs to be something that the
+  /// peer host knows about and is able to move data into
+  /// Only used when calling moveSeries or moveStudy
   void setMoveDestinationAETitle( const QString& moveDestinationAETitle );
   QString moveDestinationAETitle() const;
-
+  /// prefer to keep using the existing association to peer host when doing
+  /// multiple requests (default true)
   void setKeepAssociationOpen(const bool keepOpen);
   bool keepAssociationOpen();
-
-  /// method for database
-  void setRetrieveDatabase(QSharedPointer<ctkDICOMDatabase> dicomDatabase);
-  QSharedPointer<ctkDICOMDatabase> retrieveDatabase()const;
-
-  // Could be a slot...
-  bool retrieveSeries( const QString& studyInstanceUID,
+  /// where to insert new data sets obtained via get (must be set for
+  /// get to succeed
+  Q_INVOKABLE void setDatabase(QSharedPointer<ctkDICOMDatabase> dicomDatabase);
+  Q_INVOKABLE QSharedPointer<ctkDICOMDatabase> database()const;
+
+public slots:
+  /// Use CMOVE to ask peer host to store data to move destination
+  bool moveSeries( const QString& studyInstanceUID,
                        const QString& seriesInstanceUID );
-
-  bool retrieveStudy( const QString& studyInstanceUID );
+  /// Use CMOVE to ask peer host to store data to move destination
+  bool moveStudy( const QString& studyInstanceUID );
+  /// Use CGET to ask peer host to store data to us
+  bool getSeries( const QString& studyInstanceUID,
+                       const QString& seriesInstanceUID );
+  /// Use CGET to ask peer host to store data to us
+  bool getStudy( const QString& studyInstanceUID );
+
+signals:
+  //TODO: the signature of these signals will change
+  //from string to a more specific format when we decide
+  //what information to send
+  /// emitted when a move response has been received from dcmtk
+  void moveResponseHandled( const QString message );
+  /// emitted when a dataset is incoming from a CGET
+  void storeRequested( const QString message );
+  /// emitted when remote server sends us CGET responses
+  void retrieveStatusChanged( const QString message );
 
 protected:
   QScopedPointer<ctkDICOMRetrievePrivate> d_ptr;
 
 private:
-  void retrieve( QDir directory );
 
   Q_DECLARE_PRIVATE(ctkDICOMRetrieve);
   Q_DISABLE_COPY(ctkDICOMRetrieve);
 
+  friend class ctkDICOMRetrieveSCUPrivate;  // for access to status signals
 };
 
 

File diff suppressed because it is too large
+ 2630 - 0
Libs/DICOM/Core/ctkDcmSCU.cc


File diff suppressed because it is too large
+ 1186 - 0
Libs/DICOM/Core/ctkDcmSCU.h


+ 25 - 20
Libs/DICOM/Widgets/ctkDICOMQueryRetrieveWidget.cpp

@@ -104,22 +104,12 @@ void ctkDICOMQueryRetrieveWidgetPrivate::init()
   QObject::connect(this->CancelButton, SIGNAL(clicked()), q, SLOT(cancel()));
 
   this->results->setModel(&this->Model);
-  // TODO: use the checkable headerview when it becomes possible
-  // to select individual studies.  For now, assume that the 
-  // user will use the query terms to narrow down the transfer
-  /*
-  this->Model.setHeaderData(0, Qt::Horizontal, Qt::Unchecked, Qt::CheckStateRole);
-  QHeaderView* previousHeaderView = this->results->header();
-  ctkCheckableHeaderView* headerView =
-    new ctkCheckableHeaderView(Qt::Horizontal, this->results);
-  headerView->setClickable(previousHeaderView->isClickable());
-  headerView->setMovable(previousHeaderView->isMovable());
-  headerView->setHighlightSections(previousHeaderView->highlightSections());
-  headerView->checkableModelHelper()->setPropagateDepth(-1);
-  this->results->setHeader(headerView);
-  // headerView is hidden because it was created with a visisble parent widget 
-  headerView->setHidden(false);
-  */
+  this->results->setSelectionMode(QAbstractItemView::ExtendedSelection);
+  this->results->setSelectionBehavior(QAbstractItemView::SelectRows);
+
+  QObject::connect(this->results->selectionModel(), 
+    SIGNAL(selectionChanged (const QItemSelection &, const QItemSelection &)), 
+    q, SLOT(onSelectionChanged(const QItemSelection &, const QItemSelection &)));
 }
 
 //----------------------------------------------------------------------------
@@ -237,7 +227,6 @@ void ctkDICOMQueryRetrieveWidget::query()
   // checkable headers - allow user to select the patient/studies to retrieve
   d->Model.setDatabase(d->QueryResultDatabase.database());
 
-  d->RetrieveButton->setEnabled(d->Model.rowCount());
   progress.setValue(progress.maximum());
   d->ProgressDialog = 0;
 }
@@ -274,6 +263,8 @@ void ctkDICOMQueryRetrieveWidget::retrieve()
   progress.setMaximum(d->QueriesByStudyUID.keys().size());
   progress.open();
   progress.setValue(1);
+
+  // do the rerieval for each selected series
   foreach( QString studyUID, d->QueriesByStudyUID.keys() )
     {
     if (progress.wasCanceled())
@@ -287,10 +278,10 @@ void ctkDICOMQueryRetrieveWidget::retrieve()
 
     // Get information which server we want to get the study from and prepare request accordingly
     ctkDICOMQuery *query = d->QueriesByStudyUID[studyUID];
-    retrieve->setRetrieveDatabase( d->RetrieveDatabase );
+    retrieve->setDatabase( d->RetrieveDatabase );
     retrieve->setCallingAETitle( query->callingAETitle() );
     retrieve->setCalledAETitle( query->calledAETitle() );
-    retrieve->setCalledPort( query->port() );
+    retrieve->setPort( query->port() );
     retrieve->setHost( query->host() );
     // TODO: check the model item to see if it is checked
     // for now, assume all studies queried and shown to the user will be retrieved
@@ -300,7 +291,9 @@ void ctkDICOMQueryRetrieveWidget::retrieve()
     try
       {
       // perform the retrieve
-      retrieve->retrieveStudy ( studyUID );
+      // TODO: give the option to use MOVE instead of CGET
+      //retrieve->moveStudy ( studyUID );
+      retrieve->getStudy ( studyUID );
       }
     catch (std::exception e)
       {
@@ -386,3 +379,15 @@ void ctkDICOMQueryRetrieveWidget::updateRetrieveProgress(int value)
   d->ProgressDialog->setValue( value );
   logger.error(QString("setting value to %1").arg(value) );
 }
+
+//----------------------------------------------------------------------------
+void ctkDICOMQueryRetrieveWidget::onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
+{
+  Q_UNUSED(selected);
+  Q_UNUSED(deselected);
+  Q_D(ctkDICOMQueryRetrieveWidget);
+
+  logger.debug("Selection change");
+  d->RetrieveButton->setEnabled(d->results->selectionModel()->hasSelection());
+}
+

+ 2 - 0
Libs/DICOM/Widgets/ctkDICOMQueryRetrieveWidget.h

@@ -25,6 +25,7 @@
 
 // Qt includes 
 #include <QWidget>
+#include <QItemSelection>
 
 
 // CTK includes
@@ -47,6 +48,7 @@ public Q_SLOTS:
   void query();
   void retrieve();
   void cancel();
+  void onSelectionChanged(const QItemSelection &, const QItemSelection &);
 
 Q_SIGNALS:
   /// Signal emit when studies have been retrieved (user clicked on the

+ 0 - 5
Libs/PluginFramework/service/event/ctkEventAdmin.h

@@ -76,17 +76,12 @@ struct ctkEventAdmin
    * it under multiple topics. In that case, emitting the signal will result in
    * multiple events being send.
    *
-   * If the <code>topic</code> string is empty, a <code>std::invalid_argument</code>
-   * exception will be thrown.
-   *
    * @param publisher The owner of the signal.
    * @param signal The signal in normalized form.
    * @param topic The event topic to use.
    * @param type Qt::QueuedConnection for asynchronous delivery and
    *        Qt::DirectConnection for synchronous delivery.
    *
-   * @throws std::invalid_argument If <code>topic</code> is empty.
-   *
    * @see unpublishSignal()
    */
   virtual void publishSignal(const QObject* publisher, const char* signal,

+ 2 - 1
Plugins/org.commontk.eventadmin/dispatch/ctkEASignalPublisher_p.h

@@ -40,7 +40,8 @@ public:
   QString getSignalName() const;
   QString getTopicName() const;
 
-protected Q_SLOTS:
+  QString getSignalName() const;
+  QString getTopicName() const;
 
   void publishSyncSignal(const ctkDictionary& eventProps);
   void publishAsyncSignal(const ctkDictionary& eventProps);