Sfoglia il codice sorgente

Enabled cancel-able progress for Query and Retrieve

This uses signals and slots to set the progress text and
works okay, but we should really set up the q/r code
in threads.  Or at least we should move the loops out
of the core helper classes so that the widgets can be
more responsive and interruptible.
Steve Pieper 13 anni fa
parent
commit
650f427bb6

+ 23 - 1
Libs/DICOM/Core/ctkDICOMQuery.cpp

@@ -97,7 +97,8 @@ public:
   ctkDICOMQuerySCUPrivate SCU;
   DcmDataset*             Query;
   QStringList             StudyInstanceUIDList;
-  QList<DcmDataset*>       StudyDatasetList;
+  QList<DcmDataset*>      StudyDatasetList;
+  bool                    Canceled;
 };
 
 //------------------------------------------------------------------------------
@@ -108,6 +109,7 @@ ctkDICOMQueryPrivate::ctkDICOMQueryPrivate()
 {
   this->Query = new DcmDataset();
   this->Port = 0;
+  this->Canceled = false;
 }
 
 //------------------------------------------------------------------------------
@@ -241,6 +243,7 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database )
     emit progress("DB not open in Query");
     }
   emit progress(0);
+  if (d->Canceled) {return false;}
 
   d->StudyInstanceUIDList.clear();
   d->SCU.setAETitle ( OFString(this->callingAETitle().toStdString().c_str()) );
@@ -251,6 +254,7 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database )
   logger.error ( "Setting Transfer Syntaxes" );
   emit progress("Setting Transfer Syntaxes");
   emit progress(10);
+  if (d->Canceled) {return false;}
 
   OFList<OFString> transferSyntaxes;
   transferSyntaxes.push_back ( UID_LittleEndianExplicitTransferSyntax );
@@ -269,6 +273,7 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database )
   logger.debug ( "Negotiating Association" );
   emit progress("Negatiating Association");
   emit progress(20);
+  if (d->Canceled) {return false;}
 
   OFCondition result = d->SCU.negotiateAssociation();
   if (result.bad())
@@ -359,6 +364,7 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database )
     logger.debug("Query on study date " + dateRange);
     }
   emit progress(30);
+  if (d->Canceled) {return false;}
 
   OFList<QRResponse *> responses;
 
@@ -376,6 +382,7 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database )
     emit progress("Found useful presentation context");
     }
   emit progress(40);
+  if (d->Canceled) {return false;}
 
   OFCondition status = d->SCU.sendFINDRequest ( presentationContext, d->Query, &responses );
   if ( !status.good() )
@@ -389,6 +396,7 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database )
   logger.debug ( "Find succeded");
   emit progress("Find succeded");
   emit progress(50);
+  if (d->Canceled) {return false;}
 
   for ( OFIterator<QRResponse*> it = responses.begin(); it != responses.end(); it++ )
     {
@@ -399,6 +407,9 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database )
       OFString StudyInstanceUID;
       dataset->findAndGetOFString ( DCM_StudyInstanceUID, StudyInstanceUID );
       d->addStudyInstanceUIDAndDataset ( StudyInstanceUID.c_str(), dataset );
+      emit progress(QString("Processing: ") + QString(StudyInstanceUID.c_str()));
+      emit progress(50);
+      if (d->Canceled) {return false;}
       }
     }
 
@@ -431,6 +442,7 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database )
     logger.debug ( "Starting Series C-FIND for Study: " + StudyInstanceUID );
     emit progress(QString("Starting Series C-FIND for Study: ") + StudyInstanceUID);
     emit progress(50 + (progressRatio * i++));
+    if (d->Canceled) {return false;}
 
     d->Query->putAndInsertString ( DCM_StudyInstanceUID, StudyInstanceUID.toStdString().c_str() );
     OFList<QRResponse *> responses;
@@ -451,6 +463,8 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database )
         }
       logger.debug ( "Find succeded on Series level for Study: " + StudyInstanceUID );
       emit progress(QString("Find succeded on Series level for Study: ") + StudyInstanceUID);
+      emit progress(50 + (progressRatio * i++));
+      if (d->Canceled) {return false;}
       }
     else
       {
@@ -458,8 +472,16 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database )
       emit progress(QString("Find on Series level failed for Study: ") + StudyInstanceUID);
       }
     emit progress(50 + (progressRatio * i++));
+    if (d->Canceled) {return false;}
     }
   d->SCU.closeAssociation ( DCMSCU_RELEASE_ASSOCIATION );
   emit progress(100);
   return true;
 }
+
+//----------------------------------------------------------------------------
+void ctkDICOMQuery::cancel()
+{
+  Q_D(ctkDICOMQuery);
+  d->Canceled = true;
+}

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

@@ -105,6 +105,9 @@ Q_SIGNALS:
   /// true for success or false for error
   void done(const bool& error);
 
+public Q_SLOTS:
+  void cancel();
+
 protected:
   QScopedPointer<ctkDICOMQueryPrivate> d_ptr;
 

+ 61 - 9
Libs/DICOM/Core/ctkDICOMRetrieve.cpp

@@ -74,7 +74,8 @@ public:
     {
       if (this->retrieve)
         {
-        emit this->retrieve->debug("Got one!");
+        emit this->retrieve->progress("Got move request");
+        emit this->retrieve->progress(0);
         return this->ctkDcmSCU::handleMOVEResponse(
                         presID, response, waitForNextResponse);
         }
@@ -90,7 +91,12 @@ public:
     {
       if (this->retrieve)
         {
-        emit this->retrieve->debug("Got a store request!");
+        OFString instanceUID;
+        incomingObject->findAndGetOFString(DCM_SOPInstanceUID, instanceUID);
+        QString qInstanceUID(instanceUID.c_str());
+        emit this->retrieve->progress("Got STORE request for " + qInstanceUID);
+        emit this->retrieve->progress(0);
+        continueCGETSession = not this->retrieve->wasCanceled();
         if (this->retrieve && this->retrieve->database())
           {
           this->retrieve->database()->insert(incomingObject);
@@ -113,7 +119,9 @@ public:
     {
       if (this->retrieve)
         {
-        emit this->retrieve->debug("Got a cget response!");
+        emit this->retrieve->progress("Got CGET response");
+        emit this->retrieve->progress(0);
+        continueCGETSession = not this->retrieve->wasCanceled();
         return this->ctkDcmSCU::handleCGETResponse(presID, response, continueCGETSession);
         }
       return false;
@@ -122,13 +130,19 @@ public:
 
 
 //------------------------------------------------------------------------------
-class ctkDICOMRetrievePrivate
+class ctkDICOMRetrievePrivate: public QObject
 {
+  Q_DECLARE_PUBLIC( ctkDICOMRetrieve );
+
+protected:
+  ctkDICOMRetrieve* const q_ptr;
+
 public:
-  ctkDICOMRetrievePrivate();
+  ctkDICOMRetrievePrivate(ctkDICOMRetrieve& obj);
   ~ctkDICOMRetrievePrivate();
   /// Keep the currently negotiated connection to the 
   /// peer host open unless the connection parameters change
+  bool          WasCanceled;
   bool          KeepAssociationOpen;
   bool          ConnectionParamsChanged;
   bool          LastRetrieveType;
@@ -153,9 +167,11 @@ public:
 // ctkDICOMRetrievePrivate methods
 
 //------------------------------------------------------------------------------
-ctkDICOMRetrievePrivate::ctkDICOMRetrievePrivate()
+ctkDICOMRetrievePrivate::ctkDICOMRetrievePrivate(ctkDICOMRetrieve& obj)
+  : q_ptr(&obj)
 {
   this->Database = QSharedPointer<ctkDICOMDatabase> (0);
+  this->WasCanceled = false;
   this->KeepAssociationOpen = true;
   this->ConnectionParamsChanged = false;
   this->LastRetrieveType = RetrieveNone;
@@ -367,6 +383,8 @@ bool ctkDICOMRetrievePrivate::get ( const QString& studyInstanceUID,
                                          const QString& seriesInstanceUID,
                                          const RetrieveType retrieveType )
 {
+  Q_Q(ctkDICOMRetrieve);
+
   DcmDataset *retrieveParameters = new DcmDataset();
   if (! this->initializeSCU(studyInstanceUID, seriesInstanceUID, retrieveType, retrieveParameters) )
     {
@@ -376,6 +394,8 @@ bool ctkDICOMRetrievePrivate::get ( const QString& studyInstanceUID,
 
   // Issue request
   logger.debug ( "Sending Get Request" );
+  emit q->progress("Sending Get Request");
+  emit q->progress(0);
   OFList<RetrieveResponse*> responses;
   T_ASC_PresentationContextID presID = this->SCU.findPresentationContextID(
                                           UID_GETStudyRootQueryRetrieveInformationModel, 
@@ -391,11 +411,16 @@ bool ctkDICOMRetrievePrivate::get ( const QString& studyInstanceUID,
     return false;
     }
 
+  emit q->progress("Found Presentation Context");
+  emit q->progress(1);
 
   // do the actual move request
   OFCondition status = this->SCU.sendCGETRequest ( 
                           presID, retrieveParameters, &responses );
 
+  emit q->progress("Sent Get Request");
+  emit q->progress(2);
+
   // Close association if we do not want to explicitly keep it open
   if (!this->KeepAssociationOpen)
     {
@@ -409,9 +434,13 @@ bool ctkDICOMRetrievePrivate::get ( const QString& studyInstanceUID,
     {
     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!") );
+    emit q->progress("No Responses from Server!");
     return false;
     }
 
+  emit q->progress("Got Responses");
+  emit q->progress(3);
+
   /* 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:
@@ -452,8 +481,8 @@ bool ctkDICOMRetrievePrivate::get ( const QString& studyInstanceUID,
       return false;
       }
     }
-    // Select the last GET response to output meaningful status information
-    OFIterator<RetrieveResponse*> it = responses.begin();
+  // 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++)
     {
@@ -467,6 +496,9 @@ bool ctkDICOMRetrievePrivate::get ( const QString& studyInstanceUID,
     + QString::number(static_cast<unsigned int>((*it)->m_numberOfFailedSubops))
         + " images transfers failed");
 
+  emit q->progress("Finished Get");
+  emit q->progress(100);
+
   return true;
 }
 
@@ -475,7 +507,7 @@ bool ctkDICOMRetrievePrivate::get ( const QString& studyInstanceUID,
 
 //------------------------------------------------------------------------------
 ctkDICOMRetrieve::ctkDICOMRetrieve()
-   : d_ptr(new ctkDICOMRetrievePrivate)
+   : d_ptr(new ctkDICOMRetrievePrivate(*this))
 {
   Q_D(ctkDICOMRetrieve);
   d->SCU.retrieve = this; // give the dcmtk level access to this for emitting signals
@@ -604,6 +636,19 @@ bool ctkDICOMRetrieve::keepAssociationOpen()
   return d->KeepAssociationOpen;
 }
 
+void ctkDICOMRetrieve::setWasCanceled(const bool wasCanceled)
+{
+  Q_D(ctkDICOMRetrieve);
+  d->WasCanceled = wasCanceled;
+}
+
+//------------------------------------------------------------------------------
+bool ctkDICOMRetrieve::wasCanceled()
+{
+  Q_D(const ctkDICOMRetrieve);
+  return d->WasCanceled;
+}
+
 //------------------------------------------------------------------------------
 bool ctkDICOMRetrieve::moveStudy(const QString& studyInstanceUID)
 {
@@ -658,3 +703,10 @@ bool ctkDICOMRetrieve::getSeries(const QString& studyInstanceUID,
   return d->get ( studyInstanceUID, seriesInstanceUID, ctkDICOMRetrievePrivate::RetrieveSeries );
 }
 
+//------------------------------------------------------------------------------
+bool ctkDICOMRetrieve::cancel()
+{
+  Q_D(ctkDICOMRetrieve);
+  d->WasCanceled = true;
+}
+

+ 7 - 0
Libs/DICOM/Core/ctkDICOMRetrieve.h

@@ -42,6 +42,7 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMRetrieve : public QObject
   Q_PROPERTY(int port READ port WRITE setPort);
   Q_PROPERTY(QString moveDestinationAETitle READ moveDestinationAETitle WRITE setMoveDestinationAETitle)
   Q_PROPERTY(bool keepAssociationOpen READ keepAssociationOpen WRITE setKeepAssociationOpen)
+  Q_PROPERTY(bool wasCanceled READ wasCanceled WRITE setWasCanceled)
 
 public:
   explicit ctkDICOMRetrieve();
@@ -71,6 +72,10 @@ public:
   /// multiple requests (default true)
   void setKeepAssociationOpen(const bool keepOpen);
   bool keepAssociationOpen();
+  /// did someone cancel us during operation?
+  /// (default false)
+  void setWasCanceled(const bool wasCanceled);
+  bool wasCanceled();
   /// where to insert new data sets obtained via get (must be set for
   /// get to succeed
   Q_INVOKABLE void setDatabase(QSharedPointer<ctkDICOMDatabase> dicomDatabase);
@@ -87,6 +92,8 @@ public Q_SLOTS:
                        const QString& seriesInstanceUID );
   /// Use CGET to ask peer host to store data to us
   bool getStudy( const QString& studyInstanceUID );
+  /// Cancel the current operation
+  bool cancel();
 
 Q_SIGNALS:
   /// Signal is emitted inside the retrieve() function. It ranges from 0 to 100.

+ 42 - 12
Libs/DICOM/Widgets/ctkDICOMQueryRetrieveWidget.cpp

@@ -64,6 +64,7 @@ public:
   ctkDICOMDatabase                  QueryResultDatabase;
   QSharedPointer<ctkDICOMDatabase>  RetrieveDatabase;
   ctkDICOMModel                     Model;
+  ctkDICOMQuery                     *CurrentQuery;
   
   QProgressDialog*                  ProgressDialog;
   QString                           CurrentServer;
@@ -171,9 +172,10 @@ void ctkDICOMQueryRetrieveWidget::query()
   QLabel* progressLabel = new QLabel(tr("Initialization..."));
   progress.setLabel(progressLabel);
   d->ProgressDialog = &progress;
-  progress.setWindowModality(Qt::WindowModal);
+  progress.setWindowModality(Qt::ApplicationModal);
   progress.setMinimumDuration(0);
   progress.setValue(0);
+  progress.show();
   foreach (d->CurrentServer, d->ServerNodeWidget->selectedServerNodes())
     {
     if (progress.wasCanceled())
@@ -186,6 +188,7 @@ void ctkDICOMQueryRetrieveWidget::query()
     Q_ASSERT(parameters["CheckState"] == static_cast<int>(Qt::Checked) );
     // create a query for the current server
     ctkDICOMQuery* query = new ctkDICOMQuery;
+    d->CurrentQuery = query;
     query->setCallingAETitle(d->ServerNodeWidget->callingAETitle());
     query->setCalledAETitle(parameters["AETitle"].toString());
     query->setHost(parameters["Address"].toString());
@@ -196,19 +199,20 @@ void ctkDICOMQueryRetrieveWidget::query()
 
     try
       {
+      connect(&progress, SIGNAL(canceled()), query, SLOT(cancel()));
       connect(query, SIGNAL(progress(QString)),
-              //&progress, SLOT(setLabelText(QString)));
               progressLabel, SLOT(setText(QString)));
-      // for some reasons, setLabelText() doesn't refresh the dialog.
       connect(query, SIGNAL(progress(int)),
               this, SLOT(onQueryProgressChanged(int)));
+
       // run the query against the selected server and put results in database
       query->query ( d->QueryResultDatabase );
+
       disconnect(query, SIGNAL(progress(QString)),
-                 //&progress, SLOT(setLabelText(QString)));
                  progressLabel, SLOT(setText(QString)));
       disconnect(query, SIGNAL(progress(int)),
                  this, SLOT(onQueryProgressChanged(int)));
+      disconnect(&progress, SIGNAL(canceled()), query, SLOT(cancel()));
       }
     catch (std::exception e)
       {
@@ -225,11 +229,14 @@ void ctkDICOMQueryRetrieveWidget::query()
       }
     }
   
-  // checkable headers - allow user to select the patient/studies to retrieve
-  d->Model.setDatabase(d->QueryResultDatabase.database());
+  if (!progress.wasCanceled())
+    {
+    d->Model.setDatabase(d->QueryResultDatabase.database());
+    }
 
   progress.setValue(progress.maximum());
   d->ProgressDialog = 0;
+  d->CurrentQuery = 0;
 }
 
 //----------------------------------------------------------------------------
@@ -250,9 +257,10 @@ void ctkDICOMQueryRetrieveWidget::retrieve()
   QLabel* progressLabel = new QLabel(tr("Initialization..."));
   progress.setLabel(progressLabel);
   d->ProgressDialog = &progress;
-  progress.setWindowModality(Qt::WindowModal);
+  progress.setWindowModality(Qt::ApplicationModal);
   progress.setMinimumDuration(0);
   progress.setValue(0);
+  progress.show();
 
   QMap<QString,QVariant> serverParameters = d->ServerNodeWidget->parameters();
   ctkDICOMRetrieve *retrieve = new ctkDICOMRetrieve;
@@ -261,9 +269,6 @@ void ctkDICOMQueryRetrieveWidget::retrieve()
   // pull from GUI
   retrieve->setMoveDestinationAETitle( serverParameters["StorageAETitle"].toString() );
   int step = 0;
-  progress.setMaximum(d->QueriesByStudyUID.keys().size());
-  progress.open();
-  progress.setValue(1);
 
   // do the rerieval for each selected series
   foreach( QString studyUID, d->QueriesByStudyUID.keys() )
@@ -289,6 +294,12 @@ void ctkDICOMQueryRetrieveWidget::retrieve()
     logger.debug("About to retrieve " + studyUID + " from " + d->QueriesByStudyUID[studyUID]->host());
     logger.info ( "Starting to retrieve" );
 
+    connect(&progress, SIGNAL(canceled()), retrieve, SLOT(cancel()));
+    connect(retrieve, SIGNAL(progress(QString)),
+            progressLabel, SLOT(setText(QString)));
+    connect(retrieve, SIGNAL(progress(int)),
+            this, SLOT(updateRetrieveProgress(int)));
+
     try
       {
       // perform the retrieve
@@ -310,6 +321,13 @@ void ctkDICOMQueryRetrieveWidget::retrieve()
         break;
         }
       }
+
+    disconnect(retrieve, SIGNAL(progress(QString)),
+            progressLabel, SLOT(setText(QString)));
+    disconnect(retrieve, SIGNAL(progress(int)),
+            this, SLOT(updateRetrieveProgress(int)));
+    disconnect(&progress, SIGNAL(canceled()), retrieve, SLOT(cancel()));
+
     // Store retrieve structure for later use.
     // Comment MO: I do not think that makes much sense; you store per study one fat
     // structure including an SCU. Also, I switched the code to re-use the retrieve
@@ -322,10 +340,16 @@ void ctkDICOMQueryRetrieveWidget::retrieve()
   progressLabel->setText(tr("Retrieving Finished"));
   this->updateRetrieveProgress(progress.maximum());
 
+  QString message(tr("Retrieve Process Finished"));
+  if (retrieve->wasCanceled())
+    {
+    message = tr("Retrieve Process Canceled");
+    }
+  QMessageBox::information ( this, tr("Query Retrieve"), message );
+  emit studiesRetrieved(d->RetrievalsByStudyUID.keys());
+
   delete retrieve;
   d->ProgressDialog = 0;
-  QMessageBox::information ( this, tr("Query Retrieve"), tr("Retrieve Process Finished.") );
-  emit studiesRetrieved(d->RetrievalsByStudyUID.keys());
 }
 
 //----------------------------------------------------------------------------
@@ -343,6 +367,10 @@ void ctkDICOMQueryRetrieveWidget::onQueryProgressChanged(int value)
     {
     return;
     }
+  if (d->CurrentQuery && d->ProgressDialog->wasCanceled())
+    {
+    d->CurrentQuery->cancel();
+    }
   QStringList servers = d->ServerNodeWidget->selectedServerNodes();
   int serverIndex = servers.indexOf(d->CurrentServer);
   if (serverIndex < 0)
@@ -359,6 +387,7 @@ void ctkDICOMQueryRetrieveWidget::onQueryProgressChanged(int value)
     }
   float serverProgress = 100. / servers.size();
   d->ProgressDialog->setValue( (serverIndex + (value / 101.)) * serverProgress);
+  QApplication::processEvents();
 }
 
 //----------------------------------------------------------------------------
@@ -379,6 +408,7 @@ void ctkDICOMQueryRetrieveWidget::updateRetrieveProgress(int value)
     }
   d->ProgressDialog->setValue( value );
   logger.error(QString("setting value to %1").arg(value) );
+  QApplication::processEvents();
 }
 
 //----------------------------------------------------------------------------

+ 3 - 1
Libs/DICOM/Widgets/ctkDICOMQueryWidget.h

@@ -53,9 +53,11 @@ Q_SIGNALS:
   /// This signal is emitted when the user hits return in any of the line edits
   void returnPressed();
 
+public Q_SLOTS:
+  void onReturnPressed();
+
 protected Q_SLOTS:
   void startTimer();
-  void onReturnPressed();
 };
 
 #endif