Explorar o código

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 %!s(int64=13) %!d(string=hai) anos
pai
achega
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