From abeb25c71b2c9a64d1692a3b47745016a131f140 Mon Sep 17 00:00:00 2001 From: Davide Punzo Date: Thu, 18 Jan 2024 16:06:25 +0100 Subject: [PATCH] ENH: Add ctkDICOMJobListWidget for logging jobs activity in the UI PERF: remove unnecessary re-rendering of the thumbnails BUG: fix updates in the series widget when the update signal is from ctkDICOMStoregeListener (i.e. CMOVE) --- .../ctkDICOMVisualBrowserMain.cpp | 43 +- Libs/Core/ctkAbstractJob.cpp | 28 + Libs/Core/ctkAbstractJob.h | 28 + Libs/Core/ctkAbstractWorker.cpp | 4 +- Libs/Core/ctkJobScheduler.cpp | 18 +- Libs/Core/ctkJobScheduler.h | 3 +- Libs/DICOM/Core/Resources/dicom-schema.sql | 1 - .../Core/ctkDICOMCorePythonQtDecorators.h | 56 +- Libs/DICOM/Core/ctkDICOMEcho.cpp | 13 +- Libs/DICOM/Core/ctkDICOMEcho.h | 5 +- Libs/DICOM/Core/ctkDICOMIndexer.cpp | 8 +- Libs/DICOM/Core/ctkDICOMInserter.cpp | 2 +- Libs/DICOM/Core/ctkDICOMInserterJob.cpp | 2 +- Libs/DICOM/Core/ctkDICOMInserterWorker.cpp | 6 +- Libs/DICOM/Core/ctkDICOMJob.cpp | 2 +- Libs/DICOM/Core/ctkDICOMJob.h | 2 +- Libs/DICOM/Core/ctkDICOMJobResponseSet.cpp | 2 +- Libs/DICOM/Core/ctkDICOMQuery.cpp | 11 +- Libs/DICOM/Core/ctkDICOMQueryJob.cpp | 2 +- Libs/DICOM/Core/ctkDICOMQueryWorker.cpp | 25 +- Libs/DICOM/Core/ctkDICOMRetrieve.cpp | 7 + Libs/DICOM/Core/ctkDICOMRetrieveJob.cpp | 2 +- Libs/DICOM/Core/ctkDICOMRetrieveWorker.cpp | 24 +- Libs/DICOM/Core/ctkDICOMScheduler.cpp | 247 +++- Libs/DICOM/Core/ctkDICOMScheduler.h | 19 +- Libs/DICOM/Core/ctkDICOMStorageListener.cpp | 19 +- .../DICOM/Core/ctkDICOMStorageListenerJob.cpp | 2 +- .../Core/ctkDICOMStorageListenerWorker.cpp | 11 +- Libs/DICOM/Widgets/CMakeLists.txt | 5 +- Libs/DICOM/Widgets/Plugins/CMakeLists.txt | 5 +- .../Plugins/ctkDICOMJobListWidgetPlugin.cpp | 71 ++ .../Plugins/ctkDICOMJobListWidgetPlugin.h | 48 + .../Widgets/Plugins/ctkDICOMWidgetsPlugins.h | 2 + .../Widgets/Resources/UI/Icons/cleaning.svg | 1 + .../Widgets/Resources/UI/Icons/error.svg | 1 + .../Widgets/Resources/UI/Icons/error_red.svg | 39 + .../Widgets/Resources/UI/Icons/pending.svg | 1 + .../Widgets/Resources/UI/Icons/reset.svg | 1 + .../Widgets/Resources/UI/Icons/retry.svg | 1 + .../Widgets/Resources/UI/Icons/select_all.svg | 1 + .../DICOM/Widgets/Resources/UI/Icons/stop.svg | 1 + .../Resources/UI/Icons/visible_off.svg | 1 + .../Widgets/Resources/UI/Icons/visible_on.svg | 1 + .../Resources/UI/ctkDICOMJobListWidget.ui | 282 +++++ .../Resources/UI/ctkDICOMServerNodeWidget2.ui | 202 +-- .../UI/ctkDICOMVisualBrowserWidget.ui | 50 +- .../Widgets/Resources/UI/ctkDICOMWidget.qrc | 10 + Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp | 1121 +++++++++++++++++ Libs/DICOM/Widgets/ctkDICOMJobListWidget.h | 85 ++ .../Widgets/ctkDICOMSeriesItemWidget.cpp | 52 +- Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.h | 3 + .../Widgets/ctkDICOMServerNodeWidget2.cpp | 37 +- .../DICOM/Widgets/ctkDICOMStudyItemWidget.cpp | 2 +- .../Widgets/ctkDICOMThumbnailListWidget.cpp | 2 +- .../Widgets/ctkDICOMVisualBrowserWidget.cpp | 101 +- .../Widgets/ctkDICOMVisualBrowserWidget.h | 9 +- 56 files changed, 2383 insertions(+), 344 deletions(-) create mode 100644 Libs/DICOM/Widgets/Plugins/ctkDICOMJobListWidgetPlugin.cpp create mode 100644 Libs/DICOM/Widgets/Plugins/ctkDICOMJobListWidgetPlugin.h create mode 100644 Libs/DICOM/Widgets/Resources/UI/Icons/cleaning.svg create mode 100644 Libs/DICOM/Widgets/Resources/UI/Icons/error.svg create mode 100644 Libs/DICOM/Widgets/Resources/UI/Icons/error_red.svg create mode 100644 Libs/DICOM/Widgets/Resources/UI/Icons/pending.svg create mode 100644 Libs/DICOM/Widgets/Resources/UI/Icons/reset.svg create mode 100644 Libs/DICOM/Widgets/Resources/UI/Icons/retry.svg create mode 100644 Libs/DICOM/Widgets/Resources/UI/Icons/select_all.svg create mode 100644 Libs/DICOM/Widgets/Resources/UI/Icons/stop.svg create mode 100644 Libs/DICOM/Widgets/Resources/UI/Icons/visible_off.svg create mode 100644 Libs/DICOM/Widgets/Resources/UI/Icons/visible_on.svg create mode 100644 Libs/DICOM/Widgets/Resources/UI/ctkDICOMJobListWidget.ui create mode 100644 Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp create mode 100644 Libs/DICOM/Widgets/ctkDICOMJobListWidget.h diff --git a/Applications/ctkDICOMVisualBrowser/ctkDICOMVisualBrowserMain.cpp b/Applications/ctkDICOMVisualBrowser/ctkDICOMVisualBrowserMain.cpp index 3e00123f54..7e59a76692 100644 --- a/Applications/ctkDICOMVisualBrowser/ctkDICOMVisualBrowserMain.cpp +++ b/Applications/ctkDICOMVisualBrowser/ctkDICOMVisualBrowserMain.cpp @@ -44,6 +44,37 @@ int main(int argc, char** argv) app.setOrganizationDomain("commontk.org"); app.setApplicationName("ctkDICOM"); + QSettings settings; + QString databaseDirectory; + + // set up the database + if (argc > 1) + { + QString directory(argv[1]); + settings.setValue("DatabaseDirectory", directory); + settings.sync(); + } + + if (settings.value("DatabaseDirectory", "") == "") + { + databaseDirectory = QString("./ctkDICOM-Database"); + std::cerr << "No DatabaseDirectory on command line or in settings. Using \"" << qPrintable(databaseDirectory) << "\".\n"; + } + else + { + databaseDirectory = settings.value("DatabaseDirectory", "").toString(); + } + + QDir qdir(databaseDirectory); + if (!qdir.exists(databaseDirectory)) + { + if (!qdir.mkpath(databaseDirectory)) + { + std::cerr << "Could not create database directory \"" << qPrintable(databaseDirectory) << "\".\n"; + return EXIT_FAILURE; + } + } + // set up Qt resource files QResource::registerResource("./Resources/ctkDICOM.qrc"); @@ -67,10 +98,8 @@ int main(int argc, char** argv) ctkDirectoryButton directoryButton; directoryButton.setObjectName(QString::fromUtf8("DirectoryButton")); directoryButton.setMinimumSize(QSize(200, 30)); - if (argc > 1) - { - directoryButton.setDirectory(argv[1]); - } + directoryButton.setDirectory(databaseDirectory); + topLayout.addWidget(&directoryButton); mainLayout.addLayout(&topLayout); @@ -79,11 +108,7 @@ int main(int argc, char** argv) DICOMVisualBrowser.setObjectName(QString::fromUtf8("DICOMVisualBrowser")); DICOMVisualBrowser.setDatabaseDirectorySettingsKey("DatabaseDirectory"); DICOMVisualBrowser.setMinimumSize(QSize(1000, 1000)); - // set up the database - if (argc > 1) - { - DICOMVisualBrowser.setDatabaseDirectory(argv[1]); - } + DICOMVisualBrowser.setDatabaseDirectory(databaseDirectory); DICOMVisualBrowser.serverSettingsGroupBox()->setChecked(true); QObject::connect(&directoryButton, SIGNAL(directoryChanged(const QString&)), diff --git a/Libs/Core/ctkAbstractJob.cpp b/Libs/Core/ctkAbstractJob.cpp index 87d72a6b80..fcdc9b0071 100644 --- a/Libs/Core/ctkAbstractJob.cpp +++ b/Libs/Core/ctkAbstractJob.cpp @@ -37,6 +37,7 @@ ctkAbstractJob::ctkAbstractJob() this->MaximumNumberOfRetry = 3; this->MaximumConcurrentJobsPerType = 20; this->Priority = QThread::Priority::LowPriority; + this->CreationDateTime = QDateTime::currentDateTime(); } //---------------------------------------------------------------------------- @@ -76,6 +77,15 @@ ctkAbstractJob::JobStatus ctkAbstractJob::status() const void ctkAbstractJob::setStatus(JobStatus status) { this->Status = status; + + if (this->Status == JobStatus::Running) + { + this->StartDateTime = QDateTime::currentDateTime(); + } + else if (this->Status > JobStatus::Running) + { + this->CompletionDateTime = QDateTime::currentDateTime(); + } } //---------------------------------------------------------------------------- @@ -150,6 +160,24 @@ void ctkAbstractJob::setPriority(const QThread::Priority &priority) this->Priority = priority; } +//---------------------------------------------------------------------------- +QDateTime ctkAbstractJob::creationDateTime() const +{ + return this->CreationDateTime; +} + +//---------------------------------------------------------------------------- +QDateTime ctkAbstractJob::startDateTime() const +{ + return this->StartDateTime; +} + +//---------------------------------------------------------------------------- +QDateTime ctkAbstractJob::completionDateTime() const +{ + return this->CompletionDateTime; +} + //---------------------------------------------------------------------------- QVariant ctkAbstractJob::toVariant() { diff --git a/Libs/Core/ctkAbstractJob.h b/Libs/Core/ctkAbstractJob.h index f6dd6a9141..ee4a8b0e9e 100644 --- a/Libs/Core/ctkAbstractJob.h +++ b/Libs/Core/ctkAbstractJob.h @@ -25,6 +25,7 @@ #define __ctkAbstractJob_h // Qt includes +#include #include #include #include @@ -49,6 +50,9 @@ class CTK_CORE_EXPORT ctkAbstractJob : public QObject Q_PROPERTY(int retryDelay READ retryDelay WRITE setRetryDelay); Q_PROPERTY(bool maximumConcurrentJobsPerType READ maximumConcurrentJobsPerType WRITE setMaximumConcurrentJobsPerType); Q_PROPERTY(QThread::Priority priority READ priority WRITE setPriority); + Q_PROPERTY(QDateTime creationDateTime READ creationDateTime); + Q_PROPERTY(QDateTime startDateTime READ startDateTime); + Q_PROPERTY(QDateTime completionDateTime READ completionDateTime); public: explicit ctkAbstractJob(); @@ -116,6 +120,21 @@ class CTK_CORE_EXPORT ctkAbstractJob : public QObject void setPriority(const QThread::Priority& priority); ///@} + ///@{ + /// CreationDateTime + QDateTime creationDateTime() const; + ///@} + + ///@{ + /// StartDateTime + QDateTime startDateTime() const; + ///@} + + ///@{ + /// CompletionDateTime + QDateTime completionDateTime() const; + ///@} + /// Generate worker for job Q_INVOKABLE virtual ctkAbstractWorker* createWorker() = 0; @@ -147,6 +166,9 @@ class CTK_CORE_EXPORT ctkAbstractJob : public QObject int MaximumNumberOfRetry; int MaximumConcurrentJobsPerType; QThread::Priority Priority; + QDateTime CreationDateTime; + QDateTime StartDateTime; + QDateTime CompletionDateTime; private: Q_DISABLE_COPY(ctkAbstractJob) @@ -160,11 +182,17 @@ struct CTK_CORE_EXPORT ctkJobDetail { { this->JobClass = job.className(); this->JobUID = job.jobUID(); + this->CreationDateTime = job.creationDateTime().toString("HH:mm:ss.zzz ddd MMM yyyy"); + this->StartDateTime = job.startDateTime().toString("HH:mm:ss.zzz ddd MMM yyyy"); + this->CompletionDateTime = job.completionDateTime().toString("HH:mm:ss.zzz ddd MMM yyyy"); } virtual ~ctkJobDetail() = default; QString JobClass; QString JobUID; + QString CreationDateTime; + QString StartDateTime; + QString CompletionDateTime; }; Q_DECLARE_METATYPE(ctkJobDetail); diff --git a/Libs/Core/ctkAbstractWorker.cpp b/Libs/Core/ctkAbstractWorker.cpp index fdddcd6505..8c3b7909e7 100644 --- a/Libs/Core/ctkAbstractWorker.cpp +++ b/Libs/Core/ctkAbstractWorker.cpp @@ -127,7 +127,7 @@ void ctkAbstractWorker::onJobCanceled() this->startNextJob(); - emit this->Job->finished(); + emit this->Job->canceled(); } else if (this->Job->status() != ctkAbstractJob::JobStatus::Stopped) { @@ -135,6 +135,6 @@ void ctkAbstractWorker::onJobCanceled() } else { - emit this->Job->finished(); + emit this->Job->canceled(); } } diff --git a/Libs/Core/ctkJobScheduler.cpp b/Libs/Core/ctkJobScheduler.cpp index 141e813314..6389423b0a 100644 --- a/Libs/Core/ctkJobScheduler.cpp +++ b/Libs/Core/ctkJobScheduler.cpp @@ -248,16 +248,19 @@ ctkAbstractJob* ctkJobScheduler::getJobByUID(const QString& jobUID) } //---------------------------------------------------------------------------- -void ctkJobScheduler::waitForFinish() +void ctkJobScheduler::waitForFinish(bool waitForPersistentJobs) { Q_D(ctkJobScheduler); int numberOfPersistentJobs = this->numberOfPersistentJobs(); + if (waitForPersistentJobs) + { + numberOfPersistentJobs = 0; + } while (this->numberOfJobs() > numberOfPersistentJobs) { - QCoreApplication::processEvents(); d->ThreadPool->waitForDone(300); - } + } } //---------------------------------------------------------------------------- @@ -391,7 +394,13 @@ void ctkJobScheduler::onJobCanceled() return; } logger.debug(job->loggerReport("canceled")); - emit this->jobCanceled(job->toVariant()); + + QVariant data = job->toVariant(); + QString jobUID = job->jobUID(); + this->deleteWorker(jobUID); + this->deleteJob(jobUID); + + emit this->jobCanceled(data); } //---------------------------------------------------------------------------- @@ -468,6 +477,7 @@ void ctkJobScheduler::onQueueJobsInThreadPool() .arg(QString::number(reinterpret_cast(QThread::currentThreadId())), 16)); job->setStatus(ctkAbstractJob::JobStatus::Queued); + emit this->jobQueued(job->toVariant()); QSharedPointer worker = QSharedPointer(job->createWorker()); diff --git a/Libs/Core/ctkJobScheduler.h b/Libs/Core/ctkJobScheduler.h index 4d077698b6..e3c4690168 100644 --- a/Libs/Core/ctkJobScheduler.h +++ b/Libs/Core/ctkJobScheduler.h @@ -58,7 +58,7 @@ class CTK_CORE_EXPORT ctkJobScheduler : public QObject QSharedPointer getJobSharedByUID(const QString& jobUID); Q_INVOKABLE ctkAbstractJob* getJobByUID(const QString& jobUID); - Q_INVOKABLE void waitForFinish(); + Q_INVOKABLE void waitForFinish(bool waitForPersistentJobs = false); Q_INVOKABLE void waitForDone(int msec); Q_INVOKABLE void stopAllJobs(bool stopPersistentJobs = false); @@ -93,6 +93,7 @@ class CTK_CORE_EXPORT ctkJobScheduler : public QObject QSharedPointer threadPoolShared() const; Q_SIGNALS: + void jobQueued(QVariant data); void jobStarted(QVariant data); void jobFinished(QVariant data); void jobCanceled(QVariant data); diff --git a/Libs/DICOM/Core/Resources/dicom-schema.sql b/Libs/DICOM/Core/Resources/dicom-schema.sql index 61af7adf3b..06b352f9f4 100644 --- a/Libs/DICOM/Core/Resources/dicom-schema.sql +++ b/Libs/DICOM/Core/Resources/dicom-schema.sql @@ -74,7 +74,6 @@ CREATE TABLE 'Series' ( 'BodyPartExamined' VARCHAR(255) NULL , 'FrameOfReferenceUID' VARCHAR(64) NULL , 'AcquisitionNumber' INT NULL , - 'ContrastAgent' VARCHAR(255) NULL , 'ScanningSequence' VARCHAR(45) NULL , 'EchoNumber' INT NULL , diff --git a/Libs/DICOM/Core/ctkDICOMCorePythonQtDecorators.h b/Libs/DICOM/Core/ctkDICOMCorePythonQtDecorators.h index ac9c7de9bf..5a0bb09f91 100644 --- a/Libs/DICOM/Core/ctkDICOMCorePythonQtDecorators.h +++ b/Libs/DICOM/Core/ctkDICOMCorePythonQtDecorators.h @@ -122,60 +122,6 @@ public slots: return td->NumberOfDataSets; } - //---------------------------------------------------------------------------- - // ctkDICOMJobResponseSet - //---------------------------------------------------------------------------- - void setFilePath(ctkDICOMJobResponseSet* ts, const QString& filePath) - { - ts->setFilePath(filePath); - } - - void setCopyFile(ctkDICOMJobResponseSet* ts, bool copyFile) - { - ts->setCopyFile(copyFile); - } - - void setOverwriteExistingDataset(ctkDICOMJobResponseSet* ts, bool overwriteExistingDataset) - { - ts->setOverwriteExistingDataset(overwriteExistingDataset); - } - - void setJobType(ctkDICOMJobResponseSet* ts, ctkDICOMJobResponseSet::JobType jobType) - { - ts->setJobType(jobType); - } - - void setJobUID(ctkDICOMJobResponseSet* ts, const QString& jobUID) - { - ts->setJobUID(jobUID); - } - - void setPatientID(ctkDICOMJobResponseSet* ts, const QString& patientID) - { - ts->setPatientID(patientID); - } - - void setStudyInstanceUID(ctkDICOMJobResponseSet* ts, const QString& studyInstanceUID) - { - ts->setStudyInstanceUID(studyInstanceUID); - } - - void setSeriesInstanceUID(ctkDICOMJobResponseSet* ts, const QString& seriesInstanceUID) - { - ts->setSeriesInstanceUID(seriesInstanceUID); - } - - void setSOPInstanceUID(ctkDICOMJobResponseSet* ts, const QString& sopInstanceUID) - { - ts->setSOPInstanceUID(sopInstanceUID); - } - - void setConnectionName(ctkDICOMJobResponseSet* ts, const QString& connectionName) - { - ts->setConnectionName(connectionName); - } - - //---------------------------------------------------------------------------- // ctkDICOMDisplayedFieldGeneratorRuleFactory @@ -193,7 +139,7 @@ public slots: //---------------------------------------------------------------------------- bool registerDisplayedFieldGeneratorRule(ctkDICOMDisplayedFieldGeneratorRuleFactory* factory, - PythonQtPassOwnershipToCPP plugin) + PythonQtPassOwnershipToCPP plugin) { return factory->registerDisplayedFieldGeneratorRule(plugin); } diff --git a/Libs/DICOM/Core/ctkDICOMEcho.cpp b/Libs/DICOM/Core/ctkDICOMEcho.cpp index ad635ae387..5141611608 100644 --- a/Libs/DICOM/Core/ctkDICOMEcho.cpp +++ b/Libs/DICOM/Core/ctkDICOMEcho.cpp @@ -199,7 +199,7 @@ bool ctkDICOMEcho::echo() return false; } - logger.debug("Seding Echo"); + logger.info("Seding Echo"); // Issue ECHO request and let scu find presentation context itself (0) OFCondition status = d->SCU.sendECHORequest(0); if (!status.good()) @@ -213,3 +213,14 @@ bool ctkDICOMEcho::echo() return true; } + +//------------------------------------------------------------------------------ +void ctkDICOMEcho::cancel() +{ + Q_D(ctkDICOMEcho); + + if (d->SCU.isConnected()) + { + d->SCU.releaseAssociation(); + } +} diff --git a/Libs/DICOM/Core/ctkDICOMEcho.h b/Libs/DICOM/Core/ctkDICOMEcho.h index 296a098b02..4da544ffd8 100644 --- a/Libs/DICOM/Core/ctkDICOMEcho.h +++ b/Libs/DICOM/Core/ctkDICOMEcho.h @@ -87,7 +87,10 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMEcho : public QObject ///@} /// Echo connection. - bool echo(); + Q_INVOKABLE bool echo(); + + /// Cancel the current operation + Q_INVOKABLE void cancel(); protected: QScopedPointer d_ptr; diff --git a/Libs/DICOM/Core/ctkDICOMIndexer.cpp b/Libs/DICOM/Core/ctkDICOMIndexer.cpp index 36419794d7..cb53f35e34 100644 --- a/Libs/DICOM/Core/ctkDICOMIndexer.cpp +++ b/Libs/DICOM/Core/ctkDICOMIndexer.cpp @@ -138,7 +138,7 @@ void ctkDICOMIndexerPrivateWorker::start() imagesCountAfter = database.imagesCount(); double elapsedTimeInSeconds = timeProbe.elapsed() / 1000.0; - logger.debug(QString("DICOM indexer has updated display fields for %1 files [%2s]") + logger.info(QString("DICOM indexer has updated display fields for %1 files [%2s]") .arg(imagesCountAfter-imagesCountBefore).arg(QString::number(elapsedTimeInSeconds, 'f', 2))); // restart if new requests has been queued during displayed fields update @@ -250,7 +250,7 @@ void ctkDICOMIndexerPrivateWorker::processIndexingRequest(DICOMIndexingQueue::In } float elapsedTimeInSeconds = timeProbe.elapsed() / 1000.0; - logger.debug(QString("DICOM indexer has successfully processed %1 files [%2s]") + logger.info(QString("DICOM indexer has successfully processed %1 files [%2s]") .arg(currentFileIndex).arg(QString::number(elapsedTimeInSeconds, 'f', 2))); } @@ -279,7 +279,7 @@ void ctkDICOMIndexerPrivateWorker::writeIndexingResultsToDatabase(ctkDICOMDataba this->NumberOfInstancesInserted = 0; float elapsedTimeInSeconds = timeProbe.elapsed() / 1000.0; - logger.debug(QString("DICOM indexer has successfully inserted %1 files [%2s]") + logger.info(QString("DICOM indexer has successfully inserted %1 files [%2s]") .arg(indexingResults.count()).arg(QString::number(elapsedTimeInSeconds, 'f', 2))); } @@ -634,7 +634,7 @@ bool ctkDICOMIndexer::addDicomdir(const QString& directoryName, bool copyFile/*= } } float elapsedTimeInSeconds = timeProbe.elapsed() / 1000.0; - logger.debug(QString("DICOM indexer has successfully processed DICOMDIR in %1 [%2s]") + logger.info(QString("DICOM indexer has successfully processed DICOMDIR in %1 [%2s]") .arg(directoryName) .arg(QString::number(elapsedTimeInSeconds,'f', 2))); this->addListOfFiles(listOfInstances, copyFile); diff --git a/Libs/DICOM/Core/ctkDICOMInserter.cpp b/Libs/DICOM/Core/ctkDICOMInserter.cpp index ceaf811b27..7ba68ef86e 100644 --- a/Libs/DICOM/Core/ctkDICOMInserter.cpp +++ b/Libs/DICOM/Core/ctkDICOMInserter.cpp @@ -30,7 +30,7 @@ #include "ctkDICOMInserter.h" #include "ctkDICOMJobResponseSet.h" -static ctkLogger logger("org.commontk.dicom.DICOMQuery"); +static ctkLogger logger ("org.commontk.dicom.DICOMInserter"); //------------------------------------------------------------------------------ class ctkDICOMInserterPrivate diff --git a/Libs/DICOM/Core/ctkDICOMInserterJob.cpp b/Libs/DICOM/Core/ctkDICOMInserterJob.cpp index 0e5c9d638b..ffe464943b 100644 --- a/Libs/DICOM/Core/ctkDICOMInserterJob.cpp +++ b/Libs/DICOM/Core/ctkDICOMInserterJob.cpp @@ -26,7 +26,7 @@ #include "ctkDICOMInserterWorker.h" #include "ctkLogger.h" -static ctkLogger logger("org.commontk.dicom.ctkDICOMInserterJob"); +static ctkLogger logger ("org.commontk.dicom.DICOMInserterJob"); //------------------------------------------------------------------------------ // ctkDICOMInserterJob methods diff --git a/Libs/DICOM/Core/ctkDICOMInserterWorker.cpp b/Libs/DICOM/Core/ctkDICOMInserterWorker.cpp index 5d1fdf7045..6d1d0be464 100644 --- a/Libs/DICOM/Core/ctkDICOMInserterWorker.cpp +++ b/Libs/DICOM/Core/ctkDICOMInserterWorker.cpp @@ -32,7 +32,7 @@ #include "ctkDICOMInserterWorker_p.h" #include "ctkDICOMJobResponseSet.h" -static ctkLogger logger("org.commontk.dicom.ctkDICOMInserterWorker"); +static ctkLogger logger ("org.commontk.dicom.DICOMInserterWorker"); //------------------------------------------------------------------------------ // ctkDICOMInserterWorkerPrivate methods @@ -102,9 +102,7 @@ void ctkDICOMInserterWorker::run() if (inserterJob->status() == ctkAbstractJob::JobStatus::Stopped) { - emit inserterJob->canceled(); this->onJobCanceled(); - inserterJob->setStatus(ctkAbstractJob::JobStatus::Finished); return; } @@ -120,9 +118,7 @@ void ctkDICOMInserterWorker::run() if (inserterJob->status() == ctkAbstractJob::JobStatus::Stopped) { - emit inserterJob->canceled(); this->onJobCanceled(); - inserterJob->setStatus(ctkAbstractJob::JobStatus::Finished); return; } diff --git a/Libs/DICOM/Core/ctkDICOMJob.cpp b/Libs/DICOM/Core/ctkDICOMJob.cpp index 4fc5d8ce3c..2ed3bc3b6b 100644 --- a/Libs/DICOM/Core/ctkDICOMJob.cpp +++ b/Libs/DICOM/Core/ctkDICOMJob.cpp @@ -28,7 +28,7 @@ #include "ctkDICOMJob.h" #include "ctkDICOMJobResponseSet.h" -static ctkLogger logger("org.commontk.dicom.ctkDICOMJob"); +static ctkLogger logger ("org.commontk.dicom.DICOMJob"); //------------------------------------------------------------------------------ // ctkDICOMJob methods diff --git a/Libs/DICOM/Core/ctkDICOMJob.h b/Libs/DICOM/Core/ctkDICOMJob.h index daaca74bfb..10a3e716bf 100644 --- a/Libs/DICOM/Core/ctkDICOMJob.h +++ b/Libs/DICOM/Core/ctkDICOMJob.h @@ -57,7 +57,7 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMJob : public ctkAbstractJob Studies, Series, Instances - }; + }; Q_ENUM(DICOMLevels); ///@{ /// DICOM Level diff --git a/Libs/DICOM/Core/ctkDICOMJobResponseSet.cpp b/Libs/DICOM/Core/ctkDICOMJobResponseSet.cpp index 17f41565e6..9c263f98fa 100644 --- a/Libs/DICOM/Core/ctkDICOMJobResponseSet.cpp +++ b/Libs/DICOM/Core/ctkDICOMJobResponseSet.cpp @@ -31,7 +31,7 @@ // DCMTK includes #include -static ctkLogger logger("org.commontk.dicom.DICOMTaskResults"); +static ctkLogger logger("org.commontk.dicom.DICOMJobResponseSet"); //------------------------------------------------------------------------------ class ctkDICOMJobResponseSetPrivate : public QObject diff --git a/Libs/DICOM/Core/ctkDICOMQuery.cpp b/Libs/DICOM/Core/ctkDICOMQuery.cpp index 42b453121b..25e1982f48 100644 --- a/Libs/DICOM/Core/ctkDICOMQuery.cpp +++ b/Libs/DICOM/Core/ctkDICOMQuery.cpp @@ -334,7 +334,7 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database) } else { - logger.debug("DB not open in Query"); + logger.warn("DB not open in Query"); emit progress(tr("DB not open in Query")); } emit progress(0); @@ -392,7 +392,7 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database) } else { - logger.info("Found useful presentation context"); + logger.debug("Found useful presentation context"); emit progress(tr("Found useful presentation context")); } emit progress(40); @@ -1020,6 +1020,13 @@ void ctkDICOMQuery::cancel() d->SCU.sendCANCELRequest(d->PresentationContext); d->PresentationContext = 0; } + + if (d->SCU.isConnected()) + { + d->SCU.releaseAssociation(); + } + + d->JobResponseSets.clear(); } //---------------------------------------------------------------------------- diff --git a/Libs/DICOM/Core/ctkDICOMQueryJob.cpp b/Libs/DICOM/Core/ctkDICOMQueryJob.cpp index 1755be4836..d2682f58db 100644 --- a/Libs/DICOM/Core/ctkDICOMQueryJob.cpp +++ b/Libs/DICOM/Core/ctkDICOMQueryJob.cpp @@ -30,7 +30,7 @@ #include "ctkDICOMQueryWorker.h" #include "ctkDICOMServer.h" -static ctkLogger logger("org.commontk.dicom.ctkDICOMQueryJob"); +static ctkLogger logger ( "org.commontk.dicom.DICOMQueryJob" ); //------------------------------------------------------------------------------ // ctkDICOMQueryJobPrivate methods diff --git a/Libs/DICOM/Core/ctkDICOMQueryWorker.cpp b/Libs/DICOM/Core/ctkDICOMQueryWorker.cpp index 3c7aa721a7..651b05cfe7 100644 --- a/Libs/DICOM/Core/ctkDICOMQueryWorker.cpp +++ b/Libs/DICOM/Core/ctkDICOMQueryWorker.cpp @@ -30,7 +30,7 @@ #include "ctkDICOMScheduler.h" #include "ctkDICOMServer.h" -static ctkLogger logger("org.commontk.dicom.ctkDICOMQueryWorker"); +static ctkLogger logger ("org.commontk.dicom.DICOMQueryWorker"); //------------------------------------------------------------------------------ // ctkDICOMQueryWorkerPrivate methods @@ -110,20 +110,11 @@ void ctkDICOMQueryWorker::run() } QSharedPointer scheduler = - qSharedPointerObjectCast(this->Scheduler); - if (!scheduler) + qobject_cast>(this->Scheduler); + if (!scheduler || + queryJob->status() == ctkAbstractJob::JobStatus::Stopped) { - emit queryJob->canceled(); this->onJobCanceled(); - queryJob->setStatus(ctkAbstractJob::JobStatus::Finished); - return; - } - - if (queryJob->status() == ctkAbstractJob::JobStatus::Stopped) - { - emit queryJob->canceled(); - this->onJobCanceled(); - queryJob->setStatus(ctkAbstractJob::JobStatus::Finished); return; } @@ -139,18 +130,14 @@ void ctkDICOMQueryWorker::run() case ctkDICOMJob::DICOMLevels::Patients: if (!d->Query->queryPatients()) { - emit queryJob->canceled(); this->onJobCanceled(); - queryJob->setStatus(ctkAbstractJob::JobStatus::Finished); return; } break; case ctkDICOMJob::DICOMLevels::Studies: if (!d->Query->queryStudies(queryJob->patientID())) { - emit queryJob->canceled(); this->onJobCanceled(); - queryJob->setStatus(ctkAbstractJob::JobStatus::Finished); return; } break; @@ -158,9 +145,7 @@ void ctkDICOMQueryWorker::run() if (!d->Query->querySeries(queryJob->patientID(), queryJob->studyInstanceUID())) { - emit queryJob->canceled(); this->onJobCanceled(); - queryJob->setStatus(ctkAbstractJob::JobStatus::Finished); return; } break; @@ -169,9 +154,7 @@ void ctkDICOMQueryWorker::run() queryJob->studyInstanceUID(), queryJob->seriesInstanceUID())) { - emit queryJob->canceled(); this->onJobCanceled(); - queryJob->setStatus(ctkAbstractJob::JobStatus::Finished); return; } break; diff --git a/Libs/DICOM/Core/ctkDICOMRetrieve.cpp b/Libs/DICOM/Core/ctkDICOMRetrieve.cpp index db1646892b..dbfdfcd796 100644 --- a/Libs/DICOM/Core/ctkDICOMRetrieve.cpp +++ b/Libs/DICOM/Core/ctkDICOMRetrieve.cpp @@ -1074,4 +1074,11 @@ void ctkDICOMRetrieve::cancel() d->SCU.sendCANCELRequest(d->PresentationContext); d->PresentationContext = 0; } + + if (d->SCU.isConnected()) + { + d->SCU.releaseAssociation(); + } + + d->JobResponseSets.clear(); } diff --git a/Libs/DICOM/Core/ctkDICOMRetrieveJob.cpp b/Libs/DICOM/Core/ctkDICOMRetrieveJob.cpp index 5621e9d961..22f22c03c5 100644 --- a/Libs/DICOM/Core/ctkDICOMRetrieveJob.cpp +++ b/Libs/DICOM/Core/ctkDICOMRetrieveJob.cpp @@ -30,7 +30,7 @@ #include "ctkDICOMRetrieveWorker.h" #include "ctkDICOMServer.h" -static ctkLogger logger("org.commontk.dicom.ctkDICOMRetrieveJob"); +static ctkLogger logger ( "org.commontk.dicom.DICOMRetrieveJob" ); //------------------------------------------------------------------------------ // ctkDICOMRetrieveJobPrivate methods diff --git a/Libs/DICOM/Core/ctkDICOMRetrieveWorker.cpp b/Libs/DICOM/Core/ctkDICOMRetrieveWorker.cpp index 41db62dddd..3e5ceb2d26 100644 --- a/Libs/DICOM/Core/ctkDICOMRetrieveWorker.cpp +++ b/Libs/DICOM/Core/ctkDICOMRetrieveWorker.cpp @@ -31,7 +31,7 @@ #include "ctkDICOMScheduler.h" #include "ctkDICOMServer.h" -static ctkLogger logger("org.commontk.dicom.ctkDICOMRetrieveWorker"); +static ctkLogger logger ("org.commontk.dicom.DICOMRetrieveWorker"); //------------------------------------------------------------------------------ // ctkDICOMRetrieveWorkerPrivate methods @@ -135,9 +135,7 @@ void ctkDICOMRetrieveWorker::run() || !server || retrieveJob->status() == ctkAbstractJob::JobStatus::Stopped) { - emit retrieveJob->canceled(); this->onJobCanceled(); - retrieveJob->setStatus(ctkAbstractJob::JobStatus::Finished); return; } @@ -154,18 +152,14 @@ void ctkDICOMRetrieveWorker::run() switch(retrieveJob->dicomLevel()) { case ctkDICOMJob::DICOMLevels::Patients: - logger.info("ctkDICOMRetrieveTask : get operation for a full patient is not implemented."); - emit retrieveJob->canceled(); + logger.warn("ctkDICOMRetrieveTask : get operation for a full patient is not implemented."); this->onJobCanceled(); - retrieveJob->setStatus(ctkAbstractJob::JobStatus::Finished); return; case ctkDICOMJob::DICOMLevels::Studies: if (!d->Retrieve->getStudy(retrieveJob->studyInstanceUID(), retrieveJob->patientID())) { - emit retrieveJob->canceled(); this->onJobCanceled(); - retrieveJob->setStatus(ctkAbstractJob::JobStatus::Finished); return; } break; @@ -174,9 +168,7 @@ void ctkDICOMRetrieveWorker::run() retrieveJob->seriesInstanceUID(), retrieveJob->patientID())) { - emit retrieveJob->canceled(); this->onJobCanceled(); - retrieveJob->setStatus(ctkAbstractJob::JobStatus::Finished); return; } break; @@ -186,9 +178,7 @@ void ctkDICOMRetrieveWorker::run() retrieveJob->sopInstanceUID(), retrieveJob->patientID())) { - emit retrieveJob->canceled(); this->onJobCanceled(); - retrieveJob->setStatus(ctkAbstractJob::JobStatus::Finished); return; } break; @@ -198,7 +188,7 @@ void ctkDICOMRetrieveWorker::run() switch(retrieveJob->dicomLevel()) { case ctkDICOMJob::DICOMLevels::Patients: - logger.info("ctkDICOMRetrieveTask : move operation for a full patient is not implemented."); + logger.warn("ctkDICOMRetrieveTask : move operation for a full patient is not implemented."); retrieveJob->setStatus(ctkAbstractJob::JobStatus::Finished); emit retrieveJob->failed(); return; @@ -206,9 +196,7 @@ void ctkDICOMRetrieveWorker::run() if (!d->Retrieve->moveStudy(retrieveJob->studyInstanceUID(), retrieveJob->patientID())) { - emit retrieveJob->canceled(); this->onJobCanceled(); - retrieveJob->setStatus(ctkAbstractJob::JobStatus::Finished); return; } break; @@ -217,9 +205,7 @@ void ctkDICOMRetrieveWorker::run() retrieveJob->seriesInstanceUID(), retrieveJob->patientID())) { - emit retrieveJob->canceled(); this->onJobCanceled(); - retrieveJob->setStatus(ctkAbstractJob::JobStatus::Finished); return; } break; @@ -229,9 +215,7 @@ void ctkDICOMRetrieveWorker::run() retrieveJob->sopInstanceUID(), retrieveJob->patientID())) { - emit retrieveJob->canceled(); this->onJobCanceled(); - retrieveJob->setStatus(ctkAbstractJob::JobStatus::Finished); return; } break; @@ -242,9 +226,7 @@ void ctkDICOMRetrieveWorker::run() if (retrieveJob->status() == ctkAbstractJob::JobStatus::Stopped) { - emit retrieveJob->canceled(); this->onJobCanceled(); - retrieveJob->setStatus(ctkAbstractJob::JobStatus::Finished); return; } diff --git a/Libs/DICOM/Core/ctkDICOMScheduler.cpp b/Libs/DICOM/Core/ctkDICOMScheduler.cpp index 13b229728a..846dd36130 100644 --- a/Libs/DICOM/Core/ctkDICOMScheduler.cpp +++ b/Libs/DICOM/Core/ctkDICOMScheduler.cpp @@ -41,7 +41,7 @@ #include -static ctkLogger logger("org.commontk.dicom.DICOMJobPool"); +static ctkLogger logger ( "org.commontk.dicom.DICOMScheduler" ); //------------------------------------------------------------------------------ // ctkDICOMSchedulerPrivate methods @@ -98,6 +98,7 @@ ctkDICOMScheduler::ctkDICOMScheduler(ctkDICOMSchedulerPrivate* pimpl, QObject* p ctkDICOMScheduler::~ctkDICOMScheduler() { this->stopAllJobs(true); + this->waitForDone(2000); } //---------------------------------------------------------------------------- @@ -534,18 +535,31 @@ int ctkDICOMScheduler::getServerIndexFromName(const QString& connectionName) } //---------------------------------------------------------------------------- -void ctkDICOMScheduler::waitForFinishByUIDs(const QStringList& patientIDs, - const QStringList& studyInstanceUIDs, - const QStringList& seriesInstanceUIDs, - const QStringList& sopInstanceUIDs) +void ctkDICOMScheduler::waitForFinishByDICOMUIDs(const QStringList& patientIDs, + const QStringList& studyInstanceUIDs, + const QStringList& seriesInstanceUIDs, + const QStringList& sopInstanceUIDs) { Q_D(ctkDICOMScheduler); - if (patientIDs.count() == 0 && - studyInstanceUIDs.count() == 0 && - seriesInstanceUIDs.count() == 0 && - sopInstanceUIDs.count() == 0) + QList numberOfIDsPerLevel; + numberOfIDsPerLevel.append(patientIDs.count()); + numberOfIDsPerLevel.append(studyInstanceUIDs.count()); + numberOfIDsPerLevel.append(seriesInstanceUIDs.count()); + numberOfIDsPerLevel.append(sopInstanceUIDs.count()); + + int count = 0; + foreach (int numberOfIDs, numberOfIDsPerLevel) + { + if (numberOfIDs == 0) + { + count++; + } + } + + if (count !=3 ) { + logger.warn("ctkDICOMScheduler::waitForFinishByDICOMUIDs failed: provide only one list to the method."); return; } @@ -585,22 +599,92 @@ void ctkDICOMScheduler::waitForFinishByUIDs(const QStringList& patientIDs, } } } + } +} + +//---------------------------------------------------------------------------- +QList> ctkDICOMScheduler::getJobsByDICOMUIDs(const QStringList &patientIDs, + const QStringList &studyInstanceUIDs, + const QStringList &seriesInstanceUIDs, + const QStringList &sopInstanceUIDs) +{ + Q_D(ctkDICOMScheduler); + + QList> jobs; + + QList numberOfIDsPerLevel; + numberOfIDsPerLevel.append(patientIDs.count()); + numberOfIDsPerLevel.append(studyInstanceUIDs.count()); + numberOfIDsPerLevel.append(seriesInstanceUIDs.count()); + numberOfIDsPerLevel.append(sopInstanceUIDs.count()); + + int count = 0; + foreach (int numberOfIDs, numberOfIDsPerLevel) + { + if (numberOfIDs == 0) + { + count++; + } + } + + if (count !=3 ) + { + logger.warn("ctkDICOMScheduler::getJobsByDICOMUIDs failed: provide only one list to the method."); + return jobs; } + + foreach (QSharedPointer job, d->JobsQueue) + { + if (!job) + { + continue; + } + + ctkDICOMJob* dicomJob = qobject_cast(job.data()); + if (!dicomJob) + { + qCritical() << Q_FUNC_INFO << " failed: unexpected type of job"; + continue; + } + + if ((!dicomJob->patientID().isEmpty() && patientIDs.contains(dicomJob->patientID())) || + (!dicomJob->studyInstanceUID().isEmpty() && studyInstanceUIDs.contains(dicomJob->studyInstanceUID())) || + (!dicomJob->seriesInstanceUID().isEmpty() && seriesInstanceUIDs.contains(dicomJob->seriesInstanceUID())) || + (!dicomJob->sopInstanceUID().isEmpty() && sopInstanceUIDs.contains(dicomJob->sopInstanceUID()))) + { + jobs.push_back(job); + } + } + + return jobs; } //---------------------------------------------------------------------------- -void ctkDICOMScheduler::stopJobsByUIDs(const QStringList& patientIDs, - const QStringList& studyInstanceUIDs, - const QStringList& seriesInstanceUIDs, - const QStringList& sopInstanceUIDs) +void ctkDICOMScheduler::stopJobsByDICOMUIDs(const QStringList& patientIDs, + const QStringList& studyInstanceUIDs, + const QStringList& seriesInstanceUIDs, + const QStringList& sopInstanceUIDs) { Q_D(ctkDICOMScheduler); - if (patientIDs.count() == 0 && - studyInstanceUIDs.count() == 0 && - seriesInstanceUIDs.count() == 0 && - sopInstanceUIDs.count() == 0) + QList numberOfIDsPerLevel; + numberOfIDsPerLevel.append(patientIDs.count()); + numberOfIDsPerLevel.append(studyInstanceUIDs.count()); + numberOfIDsPerLevel.append(seriesInstanceUIDs.count()); + numberOfIDsPerLevel.append(sopInstanceUIDs.count()); + + int count = 0; + foreach (int numberOfIDs, numberOfIDsPerLevel) + { + if (numberOfIDs == 0) + { + count++; + } + } + + if (count !=3 ) { + logger.warn("ctkDICOMScheduler::stopJobsByDICOMUIDs failed: provide only one list to the method."); return; } @@ -637,6 +721,7 @@ void ctkDICOMScheduler::stopJobsByUIDs(const QStringList& patientIDs, (!dicomJob->sopInstanceUID().isEmpty() && sopInstanceUIDs.contains(dicomJob->sopInstanceUID()))) { job->setStatus(ctkAbstractJob::JobStatus::Stopped); + job->canceled(); this->deleteJob(job->jobUID()); } } @@ -673,6 +758,134 @@ void ctkDICOMScheduler::stopJobsByUIDs(const QStringList& patientIDs, } } +//---------------------------------------------------------------------------- +void ctkDICOMScheduler::stopJobsByJobUIDs(const QStringList &jobUIDs) +{ + Q_D(ctkDICOMScheduler); + + if (jobUIDs.count() == 0) + { + return; + } + + QMutexLocker ml(&d->mMutex); + + // Stops jobs without a worker (in waiting) + foreach (QSharedPointer job, d->JobsQueue) + { + if (!job) + { + continue; + } + + if (job->isPersistent()) + { + continue; + } + + if (job->status() != ctkAbstractJob::JobStatus::Initialized) + { + continue; + } + + ctkDICOMJob* dicomJob = qobject_cast(job.data()); + if (!dicomJob) + { + qCritical() << Q_FUNC_INFO << " failed: unexpected type of job"; + continue; + } + + if ((!dicomJob->jobUID().isEmpty() && jobUIDs.contains(dicomJob->jobUID()))) + { + job->setStatus(ctkAbstractJob::JobStatus::Stopped); + job->canceled(); + this->deleteJob(job->jobUID()); + } + } + + // Stops queued and running jobs + foreach (QSharedPointer worker, d->Workers) + { + QSharedPointer job = worker->jobShared(); + if (!job) + { + continue; + } + + if (job->isPersistent()) + { + continue; + } + + ctkDICOMJob* dicomJob = qobject_cast(job.data()); + if (!dicomJob) + { + qCritical() << Q_FUNC_INFO << " failed: unexpected type of job"; + continue; + } + + if ((!dicomJob->jobUID().isEmpty() && jobUIDs.contains(dicomJob->jobUID()))) + { + job->setStatus(ctkAbstractJob::JobStatus::Stopped); + worker->cancel(); + } + } +} + +//---------------------------------------------------------------------------- +void ctkDICOMScheduler::runJobs(const QMap &jobDetails) +{ + for(QString jobUID : jobDetails.keys()) + { + ctkDICOMJobDetail jd = jobDetails.value(jobUID); + if (jd.JobClass == "ctkDICOMQueryJob") + { + switch (jd.DICOMLevel) + { + case ctkDICOMJob::DICOMLevels::Patients: + this->queryPatients(); + break; + case ctkDICOMJob::DICOMLevels::Studies: + this->queryStudies(jd.PatientID); + break; + case ctkDICOMJob::DICOMLevels::Series: + this->querySeries(jd.PatientID, + jd.StudyInstanceUID); + break; + case ctkDICOMJob::DICOMLevels::Instances: + this->queryInstances(jd.PatientID, + jd.StudyInstanceUID, + jd.SeriesInstanceUID); + break; + } + } + else if (jd.JobClass == "ctkDICOMRetrieveJob") + { + switch (jd.DICOMLevel) + { + case ctkDICOMJob::DICOMLevels::Patients: + logger.warn("Retrieve Patient is not implemented"); + break; + case ctkDICOMJob::DICOMLevels::Studies: + this->retrieveStudy(jd.PatientID, + jd.StudyInstanceUID); + break; + case ctkDICOMJob::DICOMLevels::Series: + this->retrieveSeries(jd.PatientID, + jd.StudyInstanceUID, + jd.SeriesInstanceUID); + break; + case ctkDICOMJob::DICOMLevels::Instances: + this->retrieveSOPInstance(jd.PatientID, + jd.StudyInstanceUID, + jd.SeriesInstanceUID, + jd.SOPInstanceUID); + break; + } + } + } +} + //---------------------------------------------------------------------------- void ctkDICOMScheduler::raiseJobsPriorityForSeries(const QStringList& selectedSeriesInstanceUIDs, QThread::Priority priority) diff --git a/Libs/DICOM/Core/ctkDICOMScheduler.h b/Libs/DICOM/Core/ctkDICOMScheduler.h index 8f8d971a36..24b93df31b 100644 --- a/Libs/DICOM/Core/ctkDICOMScheduler.h +++ b/Libs/DICOM/Core/ctkDICOMScheduler.h @@ -36,6 +36,7 @@ class ctkAbstractJob; #include "ctkDICOMCoreExport.h" #include "ctkDICOMDatabase.h" class ctkDICOMJob; +class ctkDICOMJobDetail; class ctkDICOMIndexer; class ctkDICOMSchedulerPrivate; class ctkDICOMServer; @@ -162,16 +163,24 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMScheduler : public ctkJobScheduler ///@{ /// Jobs managment - Q_INVOKABLE void waitForFinishByUIDs(const QStringList& patientIDs = {}, + /// NOTE: methods ...ByDICOMUIDs accept only one list at the time + Q_INVOKABLE void waitForFinishByDICOMUIDs(const QStringList& patientIDs = {}, + const QStringList& studyInstanceUIDs = {}, + const QStringList& seriesInstanceUIDs = {}, + const QStringList& sopInstanceUIDs = {}); + Q_INVOKABLE void stopJobsByDICOMUIDs(const QStringList& patientIDs = {}, const QStringList& studyInstanceUIDs = {}, const QStringList& seriesInstanceUIDs = {}, const QStringList& sopInstanceUIDs = {}); - Q_INVOKABLE void stopJobsByUIDs(const QStringList& patientIDs = {}, - const QStringList& studyInstanceUIDs = {}, - const QStringList& seriesInstanceUIDs = {}, - const QStringList& sopInstanceUIDs = {}); + Q_INVOKABLE QList> getJobsByDICOMUIDs(const QStringList& patientIDs = {}, + const QStringList& studyInstanceUIDs = {}, + const QStringList& seriesInstanceUIDs = {}, + const QStringList& sopInstanceUIDs = {}); + Q_INVOKABLE void stopJobsByJobUIDs(const QStringList& jobUIDs); + Q_INVOKABLE void runJobs(const QMap& jobDetails); Q_INVOKABLE void raiseJobsPriorityForSeries(const QStringList& selectedSeriesInstanceUIDs, QThread::Priority priority = QThread::HighestPriority); + ///@} ///@{ diff --git a/Libs/DICOM/Core/ctkDICOMStorageListener.cpp b/Libs/DICOM/Core/ctkDICOMStorageListener.cpp index a4785e61f0..c8a7611ee5 100644 --- a/Libs/DICOM/Core/ctkDICOMStorageListener.cpp +++ b/Libs/DICOM/Core/ctkDICOMStorageListener.cpp @@ -34,7 +34,7 @@ // DCMTK includes #include /* for DcmStorageSCP */ -static ctkLogger logger("org.commontk.dicom.ctkDICOMStorageListener"); +static ctkLogger logger ( "org.commontk.dicom.DICOMStorageListener" ); //------------------------------------------------------------------------------ // A customized implementation so that Qt signals can be emitted @@ -49,21 +49,28 @@ class ctkDICOMStorageListenerSCUPrivate : public DcmStorageSCP }; ~ctkDICOMStorageListenerSCUPrivate() = default; - virtual OFCondition acceptAssociations() - { - return DcmSCP::acceptAssociations(); - } + virtual OFCondition acceptAssociations(); virtual OFBool stopAfterCurrentAssociation(); virtual OFBool stopAfterConnectionTimeout(); virtual OFCondition handleIncomingCommand(T_DIMSE_Message* incomingMsg, - const DcmPresentationContextInfo& presInfo); + const DcmPresentationContextInfo& presInfo); }; //------------------------------------------------------------------------------ // ctkDICOMStorageListenerSCUPrivate methods +//------------------------------------------------------------------------------ +OFCondition ctkDICOMStorageListenerSCUPrivate::acceptAssociations() +{ + if (!this->listener || this->listener->wasCanceled()) + { + return EC_IllegalCall; + } + return DcmSCP::acceptAssociations(); +} + //------------------------------------------------------------------------------ OFBool ctkDICOMStorageListenerSCUPrivate::stopAfterCurrentAssociation() { diff --git a/Libs/DICOM/Core/ctkDICOMStorageListenerJob.cpp b/Libs/DICOM/Core/ctkDICOMStorageListenerJob.cpp index c1715ee4ad..681e8bc9da 100644 --- a/Libs/DICOM/Core/ctkDICOMStorageListenerJob.cpp +++ b/Libs/DICOM/Core/ctkDICOMStorageListenerJob.cpp @@ -28,7 +28,7 @@ #include "ctkDICOMStorageListenerJob_p.h" #include "ctkDICOMStorageListenerWorker.h" -static ctkLogger logger("org.commontk.dicom.ctkDICOMStorageListenerJob"); +static ctkLogger logger ( "org.commontk.dicom.DICOMStorageListenerJob" ); //------------------------------------------------------------------------------ // ctkDICOMStorageListenerJobPrivate methods diff --git a/Libs/DICOM/Core/ctkDICOMStorageListenerWorker.cpp b/Libs/DICOM/Core/ctkDICOMStorageListenerWorker.cpp index 2641e81419..10bdf8e5bb 100644 --- a/Libs/DICOM/Core/ctkDICOMStorageListenerWorker.cpp +++ b/Libs/DICOM/Core/ctkDICOMStorageListenerWorker.cpp @@ -33,7 +33,7 @@ #include "ctkDICOMStorageListenerJob.h" #include "ctkDICOMStorageListenerWorker_p.h" -static ctkLogger logger("org.commontk.dicom.ctkDICOMStorageListenerWorker"); +static ctkLogger logger ("org.commontk.dicom.DICOMStorageListenerWorker"); //------------------------------------------------------------------------------ // ctkDICOMStorageListenerWorkerPrivate methods @@ -128,9 +128,7 @@ void ctkDICOMStorageListenerWorker::run() if (!scheduler || storageListenerJob->status() == ctkAbstractJob::JobStatus::Stopped) { - emit storageListenerJob->canceled(); this->onJobCanceled(); - storageListenerJob->setStatus(ctkAbstractJob::JobStatus::Finished); return; } @@ -144,9 +142,7 @@ void ctkDICOMStorageListenerWorker::run() if (!d->StorageListener->listen()) { - emit storageListenerJob->canceled(); this->onJobCanceled(); - storageListenerJob->setStatus(ctkAbstractJob::JobStatus::Finished); return; } @@ -198,6 +194,11 @@ void ctkDICOMStorageListenerWorker::onInsertJobDetail() QList> jobResponseSets = d->StorageListener->jobResponseSetsShared(); + if (jobResponseSets.count() == 0) + { + return; + } + scheduler->insertJobResponseSets(jobResponseSets); foreach (QSharedPointer jobResponseSet, jobResponseSets) { diff --git a/Libs/DICOM/Widgets/CMakeLists.txt b/Libs/DICOM/Widgets/CMakeLists.txt index c238cab113..df50cfd71f 100644 --- a/Libs/DICOM/Widgets/CMakeLists.txt +++ b/Libs/DICOM/Widgets/CMakeLists.txt @@ -6,7 +6,6 @@ project(CTKDICOMWidgets) set(KIT_export_directive "CTK_DICOM_WIDGETS_EXPORT") - # Source files set(KIT_SRCS ctkDICOMAppWidget.cpp @@ -23,6 +22,8 @@ set(KIT_SRCS ctkDICOMImportWidget.h ctkDICOMItemView.cpp ctkDICOMItemView.h + ctkDICOMJobListWidget.cpp + ctkDICOMJobListWidget.h ctkDICOMListenerWidget.cpp ctkDICOMListenerWidget.h ctkDICOMQueryResultsTabWidget.cpp @@ -63,6 +64,7 @@ set(KIT_MOC_SRCS ctkDICOMDirectoryListWidget.h ctkDICOMImage.h ctkDICOMImportWidget.h + ctkDICOMJobListWidget.h ctkDICOMObjectListWidget.h ctkDICOMObjectModel.h ctkDICOMQueryRetrieveWidget.h @@ -85,6 +87,7 @@ set(KIT_UI_FORMS Resources/UI/ctkDICOMBrowser.ui Resources/UI/ctkDICOMDirectoryListWidget.ui Resources/UI/ctkDICOMImportWidget.ui + Resources/UI/ctkDICOMJobListWidget.ui Resources/UI/ctkDICOMListenerWidget.ui Resources/UI/ctkDICOMQueryRetrieveWidget.ui Resources/UI/ctkDICOMQueryWidget.ui diff --git a/Libs/DICOM/Widgets/Plugins/CMakeLists.txt b/Libs/DICOM/Widgets/Plugins/CMakeLists.txt index 2ef9160efd..a9b849702e 100644 --- a/Libs/DICOM/Widgets/Plugins/CMakeLists.txt +++ b/Libs/DICOM/Widgets/Plugins/CMakeLists.txt @@ -13,6 +13,9 @@ set(PLUGIN_SRCS ctkDICOMWidgetsAbstractPlugin.cpp ctkDICOMWidgetsAbstractPlugin.h + ctkDICOMJobListWidgetPlugin.cpp + ctkDICOMJobListWidgetPlugin.h + ctkDICOMQueryRetrieveWidgetPlugin.cpp ctkDICOMQueryRetrieveWidgetPlugin.h @@ -28,7 +31,7 @@ set(PLUGIN_SRCS # Headers that should run through moc set(PLUGIN_MOC_SRCS ctkDICOMWidgetsPlugins.h - + ctkDICOMJobListWidgetPlugin.h ctkDICOMQueryRetrieveWidgetPlugin.h ctkDICOMTableManagerPlugin.h ctkDICOMTableViewPlugin.h diff --git a/Libs/DICOM/Widgets/Plugins/ctkDICOMJobListWidgetPlugin.cpp b/Libs/DICOM/Widgets/Plugins/ctkDICOMJobListWidgetPlugin.cpp new file mode 100644 index 0000000000..3513a50b7b --- /dev/null +++ b/Libs/DICOM/Widgets/Plugins/ctkDICOMJobListWidgetPlugin.cpp @@ -0,0 +1,71 @@ +/*========================================================================= + + Library: CTK + + Copyright (c) Kitware Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Davide Punzo, punzodavide@hotmail.it, + and development was supported by the Center for Intelligent Image-guided Interventions (CI3). + +=========================================================================*/ + +// CTK includes +#include "ctkDICOMJobListWidgetPlugin.h" +#include "ctkDICOMJobListWidget.h" + +//----------------------------------------------------------------------------- +ctkDICOMJobListWidgetPlugin::ctkDICOMJobListWidgetPlugin(QObject* pluginParent) + : QObject(pluginParent) +{ +} + +//----------------------------------------------------------------------------- +QWidget *ctkDICOMJobListWidgetPlugin::createWidget(QWidget *parentForWidget) +{ + ctkDICOMJobListWidget* newWidget = new ctkDICOMJobListWidget(parentForWidget); + return newWidget; +} + +//----------------------------------------------------------------------------- +QString ctkDICOMJobListWidgetPlugin::domXml() const +{ + return "\n" + "\n"; +} + +// -------------------------------------------------------------------------- +QIcon ctkDICOMJobListWidgetPlugin::icon() const +{ + return QIcon(":/Icons/listview.png"); +} + +//----------------------------------------------------------------------------- +QString ctkDICOMJobListWidgetPlugin::includeFile() const +{ + return "ctkDICOMJobListWidget.h"; +} + +//----------------------------------------------------------------------------- +bool ctkDICOMJobListWidgetPlugin::isContainer() const +{ + return false; +} + +//----------------------------------------------------------------------------- +QString ctkDICOMJobListWidgetPlugin::name() const +{ + return "ctkDICOMJobListWidget"; +} diff --git a/Libs/DICOM/Widgets/Plugins/ctkDICOMJobListWidgetPlugin.h b/Libs/DICOM/Widgets/Plugins/ctkDICOMJobListWidgetPlugin.h new file mode 100644 index 0000000000..dfd43ebd81 --- /dev/null +++ b/Libs/DICOM/Widgets/Plugins/ctkDICOMJobListWidgetPlugin.h @@ -0,0 +1,48 @@ +/*========================================================================= + + Library: CTK + + Copyright (c) Kitware Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Davide Punzo, punzodavide@hotmail.it, + and development was supported by the Center for Intelligent Image-guided Interventions (CI3). + +=========================================================================*/ + +#ifndef __ctkDICOMJobListWidgetPlugin_h +#define __ctkDICOMJobListWidgetPlugin_h + +// CTK includes +#include "ctkDICOMWidgetsAbstractPlugin.h" + +class CTK_DICOM_WIDGETS_PLUGINS_EXPORT ctkDICOMJobListWidgetPlugin + : public QObject + , public ctkDICOMWidgetsAbstractPlugin +{ + Q_OBJECT + +public: + ctkDICOMJobListWidgetPlugin(QObject *_parent = 0); + + QWidget *createWidget(QWidget *_parent); + QString domXml() const; + QIcon icon() const; + QString includeFile() const; + bool isContainer() const; + QString name() const; + +}; + +#endif diff --git a/Libs/DICOM/Widgets/Plugins/ctkDICOMWidgetsPlugins.h b/Libs/DICOM/Widgets/Plugins/ctkDICOMWidgetsPlugins.h index 36866b5a9c..f06fd6bf3f 100644 --- a/Libs/DICOM/Widgets/Plugins/ctkDICOMWidgetsPlugins.h +++ b/Libs/DICOM/Widgets/Plugins/ctkDICOMWidgetsPlugins.h @@ -27,6 +27,7 @@ // CTK includes #include "ctkDICOMWidgetsPluginsExport.h" +#include "ctkDICOMJobListWidgetPlugin.h" #include "ctkDICOMQueryRetrieveWidgetPlugin.h" #include "ctkDICOMTableManagerPlugin.h" #include "ctkDICOMTableViewPlugin.h" @@ -45,6 +46,7 @@ class CTK_DICOM_WIDGETS_PLUGINS_EXPORT ctkDICOMWidgetsPlugins QList customWidgets() const { QList plugins; + plugins << new ctkDICOMJobListWidgetPlugin; plugins << new ctkDICOMQueryRetrieveWidgetPlugin; plugins << new ctkDICOMTableManagerPlugin; plugins << new ctkDICOMTableViewPlugin; diff --git a/Libs/DICOM/Widgets/Resources/UI/Icons/cleaning.svg b/Libs/DICOM/Widgets/Resources/UI/Icons/cleaning.svg new file mode 100644 index 0000000000..cbf4542967 --- /dev/null +++ b/Libs/DICOM/Widgets/Resources/UI/Icons/cleaning.svg @@ -0,0 +1 @@ + diff --git a/Libs/DICOM/Widgets/Resources/UI/Icons/error.svg b/Libs/DICOM/Widgets/Resources/UI/Icons/error.svg new file mode 100644 index 0000000000..04380d59f7 --- /dev/null +++ b/Libs/DICOM/Widgets/Resources/UI/Icons/error.svg @@ -0,0 +1 @@ + diff --git a/Libs/DICOM/Widgets/Resources/UI/Icons/error_red.svg b/Libs/DICOM/Widgets/Resources/UI/Icons/error_red.svg new file mode 100644 index 0000000000..f3095b684c --- /dev/null +++ b/Libs/DICOM/Widgets/Resources/UI/Icons/error_red.svg @@ -0,0 +1,39 @@ + + + + + + diff --git a/Libs/DICOM/Widgets/Resources/UI/Icons/pending.svg b/Libs/DICOM/Widgets/Resources/UI/Icons/pending.svg new file mode 100644 index 0000000000..9609f3f746 --- /dev/null +++ b/Libs/DICOM/Widgets/Resources/UI/Icons/pending.svg @@ -0,0 +1 @@ + diff --git a/Libs/DICOM/Widgets/Resources/UI/Icons/reset.svg b/Libs/DICOM/Widgets/Resources/UI/Icons/reset.svg new file mode 100644 index 0000000000..fdee467d89 --- /dev/null +++ b/Libs/DICOM/Widgets/Resources/UI/Icons/reset.svg @@ -0,0 +1 @@ + diff --git a/Libs/DICOM/Widgets/Resources/UI/Icons/retry.svg b/Libs/DICOM/Widgets/Resources/UI/Icons/retry.svg new file mode 100644 index 0000000000..86858ae85e --- /dev/null +++ b/Libs/DICOM/Widgets/Resources/UI/Icons/retry.svg @@ -0,0 +1 @@ + diff --git a/Libs/DICOM/Widgets/Resources/UI/Icons/select_all.svg b/Libs/DICOM/Widgets/Resources/UI/Icons/select_all.svg new file mode 100644 index 0000000000..2d3ccae5be --- /dev/null +++ b/Libs/DICOM/Widgets/Resources/UI/Icons/select_all.svg @@ -0,0 +1 @@ + diff --git a/Libs/DICOM/Widgets/Resources/UI/Icons/stop.svg b/Libs/DICOM/Widgets/Resources/UI/Icons/stop.svg new file mode 100644 index 0000000000..2494496a9a --- /dev/null +++ b/Libs/DICOM/Widgets/Resources/UI/Icons/stop.svg @@ -0,0 +1 @@ + diff --git a/Libs/DICOM/Widgets/Resources/UI/Icons/visible_off.svg b/Libs/DICOM/Widgets/Resources/UI/Icons/visible_off.svg new file mode 100644 index 0000000000..548d1f9d71 --- /dev/null +++ b/Libs/DICOM/Widgets/Resources/UI/Icons/visible_off.svg @@ -0,0 +1 @@ + diff --git a/Libs/DICOM/Widgets/Resources/UI/Icons/visible_on.svg b/Libs/DICOM/Widgets/Resources/UI/Icons/visible_on.svg new file mode 100644 index 0000000000..e47bc82501 --- /dev/null +++ b/Libs/DICOM/Widgets/Resources/UI/Icons/visible_on.svg @@ -0,0 +1 @@ + diff --git a/Libs/DICOM/Widgets/Resources/UI/ctkDICOMJobListWidget.ui b/Libs/DICOM/Widgets/Resources/UI/ctkDICOMJobListWidget.ui new file mode 100644 index 0000000000..74a1e36d59 --- /dev/null +++ b/Libs/DICOM/Widgets/Resources/UI/ctkDICOMJobListWidget.ui @@ -0,0 +1,282 @@ + + + ctkDICOMJobListWidget + + + + 0 + 0 + 816 + 498 + + + + + + + + + Details + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + + + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SelectRows + + + false + + + true + + + 100 + + + 150 + + + true + + + false + + + + + + + + + Select all entries in the table + + + Select all + + + + :/Icons/select_all.svg:/Icons/select_all.svg + + + + + + + + 0 + 0 + + + + Retry selected canceled or failed jobs + + + Retry + + + + :/Icons/retry.svg:/Icons/retry.svg + + + + + + + + 0 + 0 + + + + Stop selected in-progress jobs + + + Stop + + + + :/Icons/stop.svg:/Icons/stop.svg + + + + + + + Filters + + + + 5 + + + 5 + + + 5 + + + 5 + + + 3 + + + + + Column used for filtering the entries with the RegEx filter + + + + + + + Reset all filters to default + + + Reset filters + + + + :/Icons/reset.svg:/Icons/reset.svg + + + + + + + + 0 + 0 + + + + Clear the completed jobs + + + Clear completed + + + + :/Icons/cleaning.svg:/Icons/cleaning.svg + + + + + + + + 0 + 0 + + + + + 150 + 0 + + + + RegEx filter + + + + + + + + 0 + 0 + + + + Show completed jobs + + + Show completed + + + + :/Icons/visible_on.svg:/Icons/visible_on.svg + + + true + + + + + + + Stop all the in-progress jobs and clear all entries in the table + + + Clear all + + + + :/Icons/delete.svg:/Icons/delete.svg + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + ctkCollapsibleGroupBox + QGroupBox +
ctkCollapsibleGroupBox.h
+ 1 +
+ + ctkComboBox + QComboBox +
ctkComboBox.h
+
+ + ctkPushButton + QPushButton +
ctkPushButton.h
+
+
+ + + + +
diff --git a/Libs/DICOM/Widgets/Resources/UI/ctkDICOMServerNodeWidget2.ui b/Libs/DICOM/Widgets/Resources/UI/ctkDICOMServerNodeWidget2.ui index 2f617941c8..821388f8ae 100644 --- a/Libs/DICOM/Widgets/Resources/UI/ctkDICOMServerNodeWidget2.ui +++ b/Libs/DICOM/Widgets/Resources/UI/ctkDICOMServerNodeWidget2.ui @@ -6,8 +6,8 @@ 0 0 - 1679 - 271 + 1202 + 306 @@ -169,105 +169,6 @@ - - - - 3 - - - - - - 0 - 0 - - - - Add host - - - - :/Icons/add.svg:/Icons/add.svg - - - - - - - - 0 - 0 - - - - Verify host - - - - :/Icons/dns.svg:/Icons/dns.svg - - - - - - - - 0 - 0 - - - - Remove host - - - - :/Icons/delete.svg:/Icons/delete.svg - - - - - - - Qt::Vertical - - - QSizePolicy::MinimumExpanding - - - - 20 - 30 - - - - - - - - - 0 - 0 - - - - - 0 - 70 - - - - Qt::Vertical - - - QDialogButtonBox::Discard|QDialogButtonBox::Save - - - true - - - - - @@ -379,6 +280,105 @@ + + + + 3 + + + + + + 0 + 0 + + + + Add host + + + + :/Icons/add.svg:/Icons/add.svg + + + + + + + + 0 + 0 + + + + Verify host + + + + :/Icons/dns.svg:/Icons/dns.svg + + + + + + + + 0 + 0 + + + + Remove host + + + + :/Icons/delete.svg:/Icons/delete.svg + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 30 + + + + + + + + + 0 + 0 + + + + + 0 + 70 + + + + Qt::Vertical + + + QDialogButtonBox::Discard|QDialogButtonBox::Save + + + true + + + + + diff --git a/Libs/DICOM/Widgets/Resources/UI/ctkDICOMVisualBrowserWidget.ui b/Libs/DICOM/Widgets/Resources/UI/ctkDICOMVisualBrowserWidget.ui index 07820eb749..5422b05067 100644 --- a/Libs/DICOM/Widgets/Resources/UI/ctkDICOMVisualBrowserWidget.ui +++ b/Libs/DICOM/Widgets/Resources/UI/ctkDICOMVisualBrowserWidget.ui @@ -10,7 +10,7 @@ 0 0 1201 - 678 + 1173 @@ -22,7 +22,7 @@ - 3 + 2 2 @@ -94,13 +94,13 @@ - 0 + 2 2 - 0 + 2 2 @@ -875,26 +875,47 @@ Please set at least one filter to query the servers + + + + Jobs + + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + + Settings - - false - - - true - - 0 + 2 2 - 0 + 2 2 @@ -908,6 +929,11 @@ Please set at least one filter to query the servers + + ctkDICOMJobListWidget + QWidget +
ctkDICOMJobListWidget.h
+
ctkCheckableComboBox QComboBox diff --git a/Libs/DICOM/Widgets/Resources/UI/ctkDICOMWidget.qrc b/Libs/DICOM/Widgets/Resources/UI/ctkDICOMWidget.qrc index 5f282c68f9..64595fe295 100644 --- a/Libs/DICOM/Widgets/Resources/UI/ctkDICOMWidget.qrc +++ b/Libs/DICOM/Widgets/Resources/UI/ctkDICOMWidget.qrc @@ -18,5 +18,15 @@ Icons/query.svg Icons/wait.svg Icons/downloading.svg + Icons/visible_off.svg + Icons/visible_on.svg + Icons/stop.svg + Icons/retry.svg + Icons/error.svg + Icons/pending.svg + Icons/cleaning.svg + Icons/reset.svg + Icons/error_red.svg + Icons/select_all.svg diff --git a/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp new file mode 100644 index 0000000000..20fe91a23e --- /dev/null +++ b/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp @@ -0,0 +1,1121 @@ +/*========================================================================= + + Library: CTK + + Copyright (c) Kitware Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Davide Punzo, punzodavide@hotmail.it, + and development was supported by the Center for Intelligent Image-guided Interventions (CI3). + +=========================================================================*/ + +//Qt includes +#include +#include + +// CTK includes +#include + +// ctkDICOMCore includes +#include "ctkDICOMJobResponseSet.h" +#include "ctkDICOMScheduler.h" + +// ctkDICOMWidgets includes +#include "ctkDICOMJobListWidget.h" +#include "ui_ctkDICOMJobListWidget.h" + +static ctkLogger logger("org.commontk.DICOM.Widgets.DICOMJobListWidget"); + +//---------------------------------------------------------------------------- +class ProgressBarDelegate : public QStyledItemDelegate +{ +public: + using QStyledItemDelegate::QStyledItemDelegate; + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + QList data = index.data().toList(); + if (data.count() != 2) + { + return; + } + + int progress = ceil(float(data.at(0).toInt()) / data.at(1).toInt() * 100); + progress = progress > 100 ? 100 : progress; + + QStyleOptionProgressBar progressBarOption; + int width = option.rect.width(); + int height = option.rect.height() - 4; + int x = option.rect.left(); + int y = option.rect.top() + 2; + progressBarOption.rect.setRect(x, y, width, height); + progressBarOption.minimum = 0; + progressBarOption.maximum = 100; + progressBarOption.progress = progress; + progressBarOption.text = QString::number(progress) + "%"; + progressBarOption.textVisible = true; + + if (progress != -1) + { + QApplication::style()->drawControl(QStyle::CE_ProgressBar, + &progressBarOption, painter); + } + } +}; + +//---------------------------------------------------------------------------- +class QCenteredItemModel : public QStandardItemModel +{ +public: + using QStandardItemModel::QStandardItemModel; + + enum JobStatus{ + Queued, + Running, + Canceled, + Failed, + Completed + }; Q_ENUM(JobStatus); + + enum Columns{ + JobType, + Status, + Progress, + CreationDateTime, + StartDateTime, + CompletionDateTime, + DICOMLevel, + PatientID, + PatientName, + PatientBirthDate, + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID, + Connection, + JobUID, + JobClass + }; Q_ENUM(Columns); + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QString getJobTypeAsString(QString jobClass, ctkDICOMJob::DICOMLevels dicomLevel); + void addJob(const ctkDICOMJobDetail &td, ctkDICOMDatabase* database); + void updateJobStatus(const ctkDICOMJobDetail &td, const JobStatus &status); + void updateProgressBar(const ctkDICOMJobDetail &td, ctkDICOMDatabase* database); + void setProgressBar(int row, const ctkDICOMJobDetail &td, ctkDICOMDatabase* database); + void clearCompletedJobs(); + static Columns getColumnIndexFromString(QString columnString); + static QString getColumnStringFromIndex(Columns columnIndex); +}; + +//---------------------------------------------------------------------------- +QVariant QCenteredItemModel::data(const QModelIndex &index, int role) const +{ + if (role == Qt::TextAlignmentRole) + { + return static_cast(Qt::AlignCenter); + } + else + { + return QStandardItemModel::data(index, role); + } +} + +//---------------------------------------------------------------------------- +QString QCenteredItemModel::getJobTypeAsString(QString jobClass, ctkDICOMJob::DICOMLevels dicomLevel) +{ + if (jobClass == "ctkDICOMQueryJob") + { + switch (dicomLevel) + { + case ctkDICOMJob::DICOMLevels::Patients: + return QObject::tr("Query patients"); + case ctkDICOMJob::DICOMLevels::Studies: + return QObject::tr("Query studies"); + case ctkDICOMJob::DICOMLevels::Series: + return QObject::tr("Query series"); + case ctkDICOMJob::DICOMLevels::Instances: + return QObject::tr("Query instances"); + } + } + else if (jobClass == "ctkDICOMRetrieveJob") + { + switch (dicomLevel) + { + case ctkDICOMJob::DICOMLevels::Patients: + return QObject::tr("Retrieve patients"); + case ctkDICOMJob::DICOMLevels::Studies: + return QObject::tr("Retrieve studies"); + case ctkDICOMJob::DICOMLevels::Series: + return QObject::tr("Retrieve series"); + case ctkDICOMJob::DICOMLevels::Instances: + return QObject::tr("Retrieve instances"); + } + } + else if (jobClass == "ctkDICOMStorageListenerJob") + { + return QObject::tr("Storage listener"); + } + + return QObject::tr(""); +} + +//---------------------------------------------------------------------------- +void QCenteredItemModel::addJob(const ctkDICOMJobDetail &td, + ctkDICOMDatabase *database) +{ + if (!database) + { + return; + } + + if (td.JobClass == "ctkDICOMInserterJob") + { + return; + } + + int row = 0; // add the job to the top + this->insertRow(row); + + QString jobType = this->getJobTypeAsString(td.JobClass, td.DICOMLevel); + this->setData(this->index(row, Columns::JobType), jobType); + this->setData(this->index(row, Columns::JobType), jobType, Qt::ToolTipRole); + + QIcon statusIcon = QIcon(":/Icons/pending.svg"); + QString statusText = QObject::tr("queued"); + QStandardItem *statusItem = new QStandardItem(QString("statusItem")); + statusItem->setIcon(statusIcon); + this->setItem(row, Columns::Status, statusItem); + this->setData(this->index(row, Columns::Status), statusText); + this->setData(this->index(row, Columns::Status), statusText, Qt::ToolTipRole); + + QList data; + data.append(0); + data.append(100); + if (td.JobClass == "ctkDICOMStorageListenerJob") + { + data[0] = -1; + } + this->setData(this->index(row, Columns::Progress), data); + + this->setData(this->index(row, Columns::CreationDateTime), td.CreationDateTime); + this->setData(this->index(row, Columns::CreationDateTime), td.CreationDateTime, Qt::ToolTipRole); + + this->setData(this->index(row, Columns::StartDateTime), QString("-")); + this->setData(this->index(row, Columns::CompletionDateTime), QString("-")); + + QString DICOMLevel; + if (td.JobClass != "ctkDICOMStorageListenerJob") + { + DICOMLevel = QMetaEnum::fromType().valueToKey(td.DICOMLevel); + } + this->setData(this->index(row, Columns::DICOMLevel), DICOMLevel); + + this->setData(this->index(row, Columns::PatientID), td.PatientID); + this->setData(this->index(row, Columns::PatientID), td.PatientID, Qt::ToolTipRole); + + QStringList patients = database->patients(); + QString patientItem; + foreach (QString patient, patients) + { + QString patientID = database->fieldForPatient("PatientID", patient); + if (patientID == td.PatientID) + { + patientItem = patient; + break; + } + } + QString patientName = database->fieldForPatient("PatientsName", patientItem); + patientName.replace(R"(^)", R"( )"); + this->setData(this->index(row, Columns::PatientName), patientName); + this->setData(this->index(row, Columns::PatientName), patientName, Qt::ToolTipRole); + + QString date = database->fieldForPatient("PatientsBirthDate", patientItem); + date.replace(QString("-"), QString("")); + date = QDate::fromString(date, "yyyyMMdd").toString(); + + this->setData(this->index(row, Columns::PatientBirthDate), date); + this->setData(this->index(row, Columns::PatientBirthDate), date, Qt::ToolTipRole); + + this->setData(this->index(row, Columns::StudyInstanceUID), td.StudyInstanceUID); + this->setData(this->index(row, Columns::SeriesInstanceUID), td.SeriesInstanceUID); + this->setData(this->index(row, Columns::SOPInstanceUID), td.SOPInstanceUID); + + this->setData(this->index(row, Columns::Connection), td.ConnectionName); + this->setData(this->index(row, Columns::Connection), td.ConnectionName, Qt::ToolTipRole); + + this->setData(this->index(row, Columns::JobUID), td.JobUID); + this->setData(this->index(row, Columns::JobClass), td.JobClass); +} + +//---------------------------------------------------------------------------- +void QCenteredItemModel::updateJobStatus(const ctkDICOMJobDetail &td, const JobStatus &status) +{ + if (td.JobClass == "ctkDICOMInserterJob") + { + return; + } + + QList list = this->findItems(td.JobUID, Qt::MatchExactly, Columns::JobUID); + if (!list.empty()) + { + int row = list.first()->row(); + QIcon statusIcon; + QString statusText; + if (status == Running) + { + statusIcon = QIcon(":/Icons/pending.svg"); + statusText = tr("in-progress"); + if (td.JobClass == "ctkDICOMQueryJob") + { + QList data; + data.append(20); + data.append(100); + this->setData(this->index(row, Columns::Progress), data); + } + } + else if (status == Failed) + { + statusIcon = QIcon(":/Icons/error.svg"); + statusText = tr("failed"); + } + else if (status == Canceled) + { + statusIcon = QIcon(":/Icons/error.svg"); + statusText = tr("canceled"); + } + else if (status == Completed) + { + statusIcon = QIcon(":/Icons/accept.svg"); + statusText = tr("completed"); + QList data; + data.append(100); + data.append(100); + this->setData(this->index(row, Columns::Progress), data); + } + + QStandardItem *statusItem = new QStandardItem(QString("statusItem")); + statusItem->setIcon(statusIcon); + this->setItem(row, Columns::Status, statusItem); + this->setData(this->index(row, Columns::Status), statusText); + this->setData(this->index(row, Columns::Status), statusText, Qt::ToolTipRole); + + if (status == Running) + { + this->setData(this->index(row, Columns::StartDateTime), td.CreationDateTime); + this->setData(this->index(row, Columns::StartDateTime), td.CreationDateTime, Qt::ToolTipRole); + } + else + { + this->setData(this->index(row, Columns::CompletionDateTime), td.CompletionDateTime); + this->setData(this->index(row, Columns::CompletionDateTime), td.CompletionDateTime, Qt::ToolTipRole); + } + } +} + +//---------------------------------------------------------------------------- +void QCenteredItemModel::updateProgressBar(const ctkDICOMJobDetail &td, ctkDICOMDatabase *database) +{ + if (td.JobType != ctkDICOMJobResponseSet::JobType::RetrieveSeries && + td.JobType != ctkDICOMJobResponseSet::JobType::StoreSOPInstance) + { + return; + } + + QList itemList = this->findItems(td.JobUID, Qt::MatchExactly, Columns::JobUID); + if (itemList.empty()) + { + return; + } + + int row = itemList.first()->row(); + QString jobClass = this->index(row, QCenteredItemModel::Columns::JobClass).data().toString(); + if (jobClass == "ctkDICOMStorageListenerJob") + { + itemList = QList(); + if (!td.SeriesInstanceUID.isEmpty()) + { + itemList = this->findItems(td.SeriesInstanceUID, Qt::MatchExactly, Columns::SeriesInstanceUID); + } + + foreach (QStandardItem* item, itemList) + { + int row = item->row(); + this->setProgressBar(row, td, database); + } + } + else + { + this->setProgressBar(row, td, database); + } +} + +//---------------------------------------------------------------------------- +void QCenteredItemModel::setProgressBar(int row, const ctkDICOMJobDetail &td, ctkDICOMDatabase *database) +{ + if (!database) + { + return; + } + + QString seriesInstanceUID = this->index(row, QCenteredItemModel::Columns::SeriesInstanceUID).data().toString(); + QString studyInstanceUID = this->index(row, QCenteredItemModel::Columns::StudyInstanceUID).data().toString(); + + if (seriesInstanceUID != td.SeriesInstanceUID || + studyInstanceUID != td.StudyInstanceUID) + { + return; + } + + QList data = this->index(row, QCenteredItemModel::Columns::Progress).data().toList(); + if (data.count() != 2) + { + return; + } + + int progress = data.at(0).toInt(); + progress += 1; + data[0] = progress; + int numberOfInstances = data.at(1).toInt(); + numberOfInstances = database->instancesForSeries(td.SeriesInstanceUID).count(); + data[1] = numberOfInstances; + this->setData(this->index(row, Columns::Progress), data); +} + +//---------------------------------------------------------------------------- +void QCenteredItemModel::clearCompletedJobs() +{ + QList list = this->findItems(tr("completed"), Qt::MatchRegularExpression, Columns::Status); + foreach (QStandardItem* item, list) + { + this->removeRow(item->row()); + } +} + +//---------------------------------------------------------------------------- +QCenteredItemModel::Columns QCenteredItemModel::getColumnIndexFromString(QString columnString) +{ + if (columnString == QObject::tr("Type")) + { + return Columns::JobType; + } + else if (columnString == QObject::tr("Status")) + { + return Columns::Status; + } + else if (columnString == QObject::tr("Progress")) + { + return Columns::Progress; + } + else if (columnString == QObject::tr("Time and Date")) + { + return Columns::CreationDateTime; + } + else if (columnString == QObject::tr("Starting Time and Date")) + { + return Columns::StartDateTime; + } + else if (columnString == QObject::tr("Completion Time and Date")) + { + return Columns::CompletionDateTime; + } + else if (columnString == QObject::tr("DICOM Level")) + { + return Columns::DICOMLevel; + } + else if (columnString == QObject::tr("Patient ID")) + { + return Columns::PatientID; + } + else if (columnString == QObject::tr("Patient Name")) + { + return Columns::PatientName; + } + else if (columnString == QObject::tr("Birth Date")) + { + return Columns::PatientBirthDate; + } + else if (columnString == QObject::tr("Study UID")) + { + return Columns::StudyInstanceUID; + } + else if (columnString == QObject::tr("Series UID")) + { + return Columns::SeriesInstanceUID; + } + else if (columnString == QObject::tr("SOP UID")) + { + return Columns::SOPInstanceUID; + } + else if (columnString == QObject::tr("Connection")) + { + return Columns::Connection; + } + else if (columnString == QObject::tr("Job UID")) + { + return Columns::JobUID; + } + else if (columnString == QObject::tr("Class")) + { + return Columns::JobClass; + } + else + { + return Columns::JobClass; + } +} + +//---------------------------------------------------------------------------- +QString QCenteredItemModel::getColumnStringFromIndex(Columns columnIndex) +{ + switch (columnIndex) + { + case Columns::JobType: + return QObject::tr("Type"); + case Columns::Status: + return QObject::tr("Status"); + case Columns::Progress: + return QObject::tr("Progress"); + case Columns::CreationDateTime: + return QObject::tr("Time and Date"); + case Columns::StartDateTime: + return QObject::tr("Starting Time and Date"); + case Columns::CompletionDateTime: + return QObject::tr("Completion Time and Date"); + case Columns::DICOMLevel: + return QObject::tr("DICOM Level"); + case Columns::PatientID: + return QObject::tr("Patient ID"); + case Columns::PatientName: + return QObject::tr("Patient Name"); + case Columns::PatientBirthDate: + return QObject::tr("Birth Date"); + case Columns::StudyInstanceUID: + return QObject::tr("Study UID"); + case Columns::SeriesInstanceUID: + return QObject::tr("Series UID"); + case Columns::SOPInstanceUID: + return QObject::tr("SOP UID"); + case Columns::Connection: + return QObject::tr("Connection"); + case Columns::JobUID: + return QObject::tr("Job UID"); + case Columns::JobClass: + return QObject::tr("Class"); + default: + return QObject::tr(""); + } +} + +//---------------------------------------------------------------------------- +class ctkDICOMJobListWidgetPrivate: public Ui_ctkDICOMJobListWidget +{ + Q_DECLARE_PUBLIC( ctkDICOMJobListWidget ); + +protected: + ctkDICOMJobListWidget* const q_ptr; + +public: + ctkDICOMJobListWidgetPrivate(ctkDICOMJobListWidget& obj); + ~ctkDICOMJobListWidgetPrivate(); + + void init(); + void disconnectScheduler(); + void connectScheduler(); + void setFilterKeyColumn(QString); + void updateJobsDetailsWidget(); + + QSharedPointer Scheduler; + QSharedPointer proxyModel; + QSharedPointer showCompletedProxyModel; + QSharedPointer dataModel; +}; + +//---------------------------------------------------------------------------- +// ctkDICOMJobListWidgetPrivate methods + +//---------------------------------------------------------------------------- +ctkDICOMJobListWidgetPrivate::ctkDICOMJobListWidgetPrivate(ctkDICOMJobListWidget& obj) + : q_ptr(&obj) +{ + this->Scheduler = nullptr; + this->proxyModel = nullptr; + this->showCompletedProxyModel = nullptr; + this->dataModel = nullptr; +} + +//---------------------------------------------------------------------------- +ctkDICOMJobListWidgetPrivate::~ctkDICOMJobListWidgetPrivate() +{ +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidgetPrivate::init() +{ + Q_Q(ctkDICOMJobListWidget); + this->setupUi(q); + + QStringList filteringColumnNames; + filteringColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::JobType)); + filteringColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::Status)); + filteringColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::CreationDateTime)); + filteringColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::PatientID)); + filteringColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::PatientName)); + filteringColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::PatientBirthDate)); + filteringColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::Connection)); + + this->FilterColumnComboBox->addItems(filteringColumnNames); + + QStringList allColumnNames; + allColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::JobType)); + allColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::Status)); + allColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::Progress)); + allColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::CreationDateTime)); + allColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::StartDateTime)); + allColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::CompletionDateTime)); + allColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::DICOMLevel)); + allColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::PatientID)); + allColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::PatientName)); + allColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::PatientBirthDate)); + allColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::StudyInstanceUID)); + allColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::SeriesInstanceUID)); + allColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::SOPInstanceUID)); + allColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::Connection)); + allColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::JobUID)); + allColumnNames.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::JobClass)); + + this->dataModel = QSharedPointer(new QCenteredItemModel(0, allColumnNames.count(), q)); + this->dataModel->setHorizontalHeaderLabels(allColumnNames); + + this->proxyModel = QSharedPointer(new QSortFilterProxyModel); + this->proxyModel->setSourceModel(this->dataModel.data()); + + this->showCompletedProxyModel = QSharedPointer(new QSortFilterProxyModel); + this->showCompletedProxyModel->setSourceModel(this->proxyModel.data()); + this->showCompletedProxyModel->setFilterKeyColumn(QCenteredItemModel::Columns::Status); + + this->JobsView->setAlternatingRowColors(false); + this->JobsView->setModel(this->showCompletedProxyModel.data()); + this->JobsView->setItemDelegateForColumn(QCenteredItemModel::Columns::Progress, new ProgressBarDelegate(q)); + + this->JobsView->setColumnHidden(QCenteredItemModel::Columns::StartDateTime, true); + this->JobsView->setColumnHidden(QCenteredItemModel::Columns::CompletionDateTime, true); + this->JobsView->setColumnHidden(QCenteredItemModel::Columns::DICOMLevel, true); + this->JobsView->setColumnHidden(QCenteredItemModel::Columns::StudyInstanceUID, true); + this->JobsView->setColumnHidden(QCenteredItemModel::Columns::SeriesInstanceUID, true); + this->JobsView->setColumnHidden(QCenteredItemModel::Columns::SOPInstanceUID, true); + this->JobsView->setColumnHidden(QCenteredItemModel::Columns::JobUID, true); + this->JobsView->setColumnHidden(QCenteredItemModel::Columns::JobClass, true); + + QObject::connect(this->JobsView->selectionModel(), &QItemSelectionModel::selectionChanged, + q, &ctkDICOMJobListWidget::onJobsViewSelectionChanged); + + QObject::connect(this->FilterLineEdit, SIGNAL(textChanged(QString)), + q, SLOT(onFilterTextChanged(QString))); + QObject::connect(this->FilterColumnComboBox, SIGNAL(currentTextChanged(QString)), + q, SLOT(onFilterColumnChanged(QString))); + + this->StopButton->setEnabled(false); + this->RetryButton->setEnabled(false); + + QObject::connect(this->SelectAllPushButton, SIGNAL(clicked()), + q, SLOT(onSelectAllButtonClicked())); + QObject::connect(this->StopButton, SIGNAL(clicked()), + q, SLOT(onStopButtonClicked())); + QObject::connect(this->RetryButton, SIGNAL(clicked()), + q, SLOT(onRetryButtonClicked())); + QObject::connect(this->ResetFiltersPushButton, SIGNAL(clicked()), + q, SLOT(onResetFiltersButtonClicked())); + QObject::connect(this->ShowCompletedButton, SIGNAL(toggled(bool)), + q, SLOT(onShowCompletedButtonToggled(bool))); + q->onShowCompletedButtonToggled(false); + QObject::connect(this->ClearCompletedCompletedButton, SIGNAL(clicked()), + q, SLOT(onClearCompletedButtonClicked())); + QObject::connect(this->ClearAllPushButton, SIGNAL(clicked()), + q, SLOT(onClearAllButtonClicked())); + + this->FiltersCollapsibleGroupBox->setCollapsed(true); + this->DetailsCollapsibleGroupBox->setCollapsed(true); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidgetPrivate::disconnectScheduler() +{ + Q_Q(ctkDICOMJobListWidget); + if (!this->Scheduler) + { + return; + } + + ctkDICOMJobListWidget::disconnect(this->Scheduler.data(), SIGNAL(jobQueued(QVariant)), + q, SLOT(onJobQueued(QVariant))); + ctkDICOMJobListWidget::disconnect(this->Scheduler.data(), SIGNAL(jobStarted(QVariant)), + q, SLOT(onJobStarted(QVariant))); + ctkDICOMJobListWidget::disconnect(this->Scheduler.data(), SIGNAL(jobCanceled(QVariant)), + q, SLOT(onJobCanceled(QVariant))); + ctkDICOMJobListWidget::disconnect(this->Scheduler.data(), SIGNAL(jobFinished(QVariant)), + q, SLOT(onJobFinished(QVariant))); + ctkDICOMJobListWidget::disconnect(this->Scheduler.data(), SIGNAL(jobFailed(QVariant)), + q, SLOT(onJobFailed(QVariant))); + ctkDICOMJobListWidget::disconnect(this->Scheduler.data(), SIGNAL(progressJobDetail(QVariant)), + q, SLOT(onProgressJobDetail(QVariant))); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidgetPrivate::connectScheduler() +{ + Q_Q(ctkDICOMJobListWidget); + if (!this->Scheduler) + { + return; + } + + ctkDICOMJobListWidget::connect(this->Scheduler.data(), SIGNAL(jobQueued(QVariant)), + q, SLOT(onJobQueued(QVariant))); + ctkDICOMJobListWidget::connect(this->Scheduler.data(), SIGNAL(jobStarted(QVariant)), + q, SLOT(onJobStarted(QVariant))); + ctkDICOMJobListWidget::connect(this->Scheduler.data(), SIGNAL(jobCanceled(QVariant)), + q, SLOT(onJobCanceled(QVariant))); + ctkDICOMJobListWidget::connect(this->Scheduler.data(), SIGNAL(jobFinished(QVariant)), + q, SLOT(onJobFinished(QVariant))); + ctkDICOMJobListWidget::connect(this->Scheduler.data(), SIGNAL(jobFailed(QVariant)), + q, SLOT(onJobFailed(QVariant))); + ctkDICOMJobListWidget::connect(this->Scheduler.data(), SIGNAL(progressJobDetail(QVariant)), + q, SLOT(onProgressJobDetail(QVariant))); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidgetPrivate::setFilterKeyColumn(QString text) +{ + int columnIndex = QCenteredItemModel::getColumnIndexFromString(text); + this->proxyModel->setFilterKeyColumn(columnIndex); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidgetPrivate::updateJobsDetailsWidget() +{ + this->DetailsTextBrowser->clear(); + + QItemSelectionModel *select = this->JobsView->selectionModel(); + if (!select->hasSelection()) + { + return; + } + + QString detailsText; + QModelIndexList selectedRows = select->selectedRows(); + foreach (QModelIndex rowIndex, selectedRows) + { + detailsText.append(QString("\n || --------------------------------------------------------" + "------------------------------------------------------- ||\n\n")); + int row = rowIndex.row(); + QString jobType = this->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::JobType).data().toString(); + QString status = this->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::Status).data().toString(); + QString creationDateTime = this->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::CreationDateTime).data().toString(); + QString startDateTime = this->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::StartDateTime).data().toString(); + QString completionDateTime = this->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::CompletionDateTime).data().toString(); + QString dicomLevel = this->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::DICOMLevel).data().toString(); + QString patientID = this->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::PatientID).data().toString(); + QString patientName = this->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::PatientName).data().toString(); + QString patientBirthDate = this->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::PatientBirthDate).data().toString(); + QString studyInstanceUID = this->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::StudyInstanceUID).data().toString(); + QString seriesInstanceUID = this->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::SeriesInstanceUID).data().toString(); + QString sopInstanceUID = this->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::SOPInstanceUID).data().toString(); + QString connection = this->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::Connection).data().toString(); + QString jobUID = this->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::JobUID).data().toString(); + QString jobClass = this->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::JobClass).data().toString(); + + detailsText.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::JobType)); + detailsText.append(QString(" : ") + jobType + QString(" \n")); + detailsText.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::Status)); + detailsText.append(QString(" : ") + status + QString(" \n")); + detailsText.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::CreationDateTime)); + detailsText.append(QString(" : ") + creationDateTime + QString(" \n")); + detailsText.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::StartDateTime)); + detailsText.append(QString(" : ") + startDateTime + QString(" \n")); + detailsText.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::CompletionDateTime)); + detailsText.append(QString(" : ") + completionDateTime + QString(" \n")); + detailsText.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::DICOMLevel)); + detailsText.append(QString(" : ") + dicomLevel + QString(" \n")); + detailsText.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::PatientID)); + detailsText.append(QString(" : ") + patientID + QString(" \n")); + detailsText.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::PatientName)); + detailsText.append(QString(" : ") + patientName + QString(" \n")); + detailsText.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::PatientBirthDate)); + detailsText.append(QString(" : ") + patientBirthDate + QString(" \n")); + detailsText.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::StudyInstanceUID)); + detailsText.append(QString(" : ") + studyInstanceUID + QString(" \n")); + detailsText.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::SeriesInstanceUID)); + detailsText.append(QString(" : ") + seriesInstanceUID + QString(" \n")); + detailsText.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::SOPInstanceUID)); + detailsText.append(QString(" : ") + sopInstanceUID + QString(" \n")); + detailsText.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::Connection)); + detailsText.append(QString(" : ") + connection + QString(" \n")); + detailsText.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::JobUID)); + detailsText.append(QString(" : ") + jobUID + QString(" \n")); + detailsText.append(QCenteredItemModel::getColumnStringFromIndex(QCenteredItemModel::Columns::JobClass)); + detailsText.append(QString(" : ") + jobClass + QString(" \n")); + + detailsText.append(QString("Logger : ") + QString(" \n")); + // To Do: get DCMTK logging stream per job + } + + this->DetailsTextBrowser->setPlainText(detailsText); +} + +//---------------------------------------------------------------------------- +// ctkDICOMJobListWidget methods + +//---------------------------------------------------------------------------- +ctkDICOMJobListWidget::ctkDICOMJobListWidget(QWidget* parentWidget) + : Superclass(parentWidget) + , d_ptr(new ctkDICOMJobListWidgetPrivate(*this)) +{ + Q_D(ctkDICOMJobListWidget); + d->init(); +} + +//---------------------------------------------------------------------------- +ctkDICOMJobListWidget::~ctkDICOMJobListWidget() +{ +} + +//---------------------------------------------------------------------------- +static void skipDelete(QObject* obj) +{ + Q_UNUSED(obj); + // this deleter does not delete the object from memory + // useful if the pointer is not owned by the smart pointer +} + +//---------------------------------------------------------------------------- +ctkDICOMScheduler* ctkDICOMJobListWidget::scheduler()const +{ + Q_D(const ctkDICOMJobListWidget); + return d->Scheduler.data(); +} + +//---------------------------------------------------------------------------- +QSharedPointer ctkDICOMJobListWidget::schedulerShared()const +{ + Q_D(const ctkDICOMJobListWidget); + return d->Scheduler; +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::setScheduler(ctkDICOMScheduler& scheduler) +{ + Q_D(ctkDICOMJobListWidget); + d->disconnectScheduler(); + d->Scheduler = QSharedPointer(&scheduler, skipDelete); + d->connectScheduler(); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::setScheduler(QSharedPointer scheduler) +{ + Q_D(ctkDICOMJobListWidget); + d->disconnectScheduler(); + d->Scheduler = scheduler; + d->connectScheduler(); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::onJobQueued(QVariant data) +{ + Q_D(ctkDICOMJobListWidget); + ctkDICOMJobDetail td = data.value(); + + if(td.JobClass.isEmpty()) + { + return; + } + + d->dataModel->addJob(td, d->Scheduler->dicomDatabase()); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::onJobStarted(QVariant data) +{ + Q_D(ctkDICOMJobListWidget); + ctkDICOMJobDetail td = data.value(); + + if(td.JobClass.isEmpty()) + { + return; + } + + d->dataModel->updateJobStatus(td, QCenteredItemModel::Running); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::onJobFinished(QVariant data) +{ + Q_D(ctkDICOMJobListWidget); + ctkDICOMJobDetail td = data.value(); + + if(td.JobClass.isEmpty()) + { + return; + } + + d->dataModel->updateJobStatus(td, QCenteredItemModel::Completed); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::onProgressJobDetail(QVariant data) +{ + Q_D(ctkDICOMJobListWidget); + ctkDICOMJobDetail td = data.value(); + + if(td.JobType == ctkDICOMJobResponseSet::JobType::None) + { + return; + } + + d->dataModel->updateProgressBar(td, d->Scheduler->dicomDatabase()); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::onJobCanceled(QVariant data) +{ + Q_D(ctkDICOMJobListWidget); + ctkDICOMJobDetail td = data.value(); + + if(td.JobClass.isEmpty()) + { + return; + } + + d->dataModel->updateJobStatus(td, QCenteredItemModel::Canceled); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::onJobFailed(QVariant data) +{ + Q_D(ctkDICOMJobListWidget); + ctkDICOMJobDetail td = data.value(); + + if(td.JobClass.isEmpty()) + { + return; + } + + d->dataModel->updateJobStatus(td, QCenteredItemModel::Failed); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::onFilterTextChanged(QString text) +{ + Q_D(ctkDICOMJobListWidget); + d->proxyModel->setFilterRegExp(text); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::onFilterColumnChanged(QString text) +{ + Q_D(ctkDICOMJobListWidget); + d->setFilterKeyColumn(text); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::onJobsViewSelectionChanged() +{ + Q_D(ctkDICOMJobListWidget); + QItemSelectionModel *select = d->JobsView->selectionModel(); + QModelIndexList selectedRows = select->selectedRows(); + + bool failedJobSelected = false; + foreach (QModelIndex rowIndex, selectedRows) + { + int row = rowIndex.row(); + QString status = d->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::Status).data().toString(); + QString jobClass = d->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::JobClass).data().toString(); + + if ((status == tr("failed") || status == tr("canceled")) && + (jobClass == "ctkDICOMQueryJob" || jobClass == "ctkDICOMRetrieveJob")) + { + failedJobSelected = true; + break; + } + } + d->RetryButton->setEnabled(failedJobSelected); + + bool inProgressJobSelected = false; + foreach (QModelIndex rowIndex, selectedRows) + { + int row = rowIndex.row(); + QString status = d->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::Status).data().toString(); + QString jobUID = d->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::JobUID).data().toString(); + QString jobClass = d->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::JobClass).data().toString(); + + if ((status == tr("in-progress") || status == tr("queued")) && + jobClass != "ctkDICOMStorageListenerJob") + { + inProgressJobSelected = true; + break; + } + } + d->StopButton->setEnabled(inProgressJobSelected); + + d->updateJobsDetailsWidget(); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::onSelectAllButtonClicked() +{ + Q_D(ctkDICOMJobListWidget); + d->JobsView->selectAll(); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::onStopButtonClicked() +{ + Q_D(ctkDICOMJobListWidget); + QStringList jobsUIDsToStop; + QItemSelectionModel *select = d->JobsView->selectionModel(); + QModelIndexList selectedRows = select->selectedRows(); + foreach (QModelIndex rowIndex, selectedRows) + { + int row = rowIndex.row(); + QString status = d->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::Status).data().toString(); + QString jobUID = d->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::JobUID).data().toString(); + if (status == tr("in-progress") || status == tr("queued")) + { + jobsUIDsToStop.append(jobUID); + } + } + + d->Scheduler->stopJobsByJobUIDs(jobsUIDsToStop); + d->JobsView->clearSelection(); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::onRetryButtonClicked() +{ + Q_D(ctkDICOMJobListWidget); + QMap jobsUIDsToRetry; + QItemSelectionModel *select = d->JobsView->selectionModel(); + QModelIndexList selectedRows = select->selectedRows(); + foreach (QModelIndex rowIndex, selectedRows) + { + int row = rowIndex.row(); + QString status = d->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::Status).data().toString(); + QString jobClass = d->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::JobClass).data().toString(); + QString jobUID = d->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::JobUID).data().toString(); + if (status != tr("failed") && status != tr("canceled")) + { + continue; + } + + if (jobClass != "ctkDICOMQueryJob" && jobClass != "ctkDICOMRetrieveJob") + { + continue; + } + + ctkDICOMJobDetail jobDetail; + jobDetail.JobClass = d->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::JobClass).data().toString(); + + QString DICOMLevelString = d->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::DICOMLevel).data().toString(); + ctkDICOMJob::DICOMLevels DICOMLevel = ctkDICOMJob::DICOMLevels::Patients; + if (DICOMLevelString == "Studies") + { + DICOMLevel = ctkDICOMJob::DICOMLevels::Studies; + } + else if (DICOMLevelString == "Series") + { + DICOMLevel = ctkDICOMJob::DICOMLevels::Series; + } + else if (DICOMLevelString == "Instances") + { + DICOMLevel = ctkDICOMJob::DICOMLevels::Instances; + } + + jobDetail.DICOMLevel = DICOMLevel; + jobDetail.PatientID = d->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::PatientID).data().toString(); + jobDetail.StudyInstanceUID = d->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::StudyInstanceUID).data().toString(); + jobDetail.SeriesInstanceUID = d->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::SeriesInstanceUID).data().toString(); + jobDetail.SOPInstanceUID = d->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::SOPInstanceUID).data().toString(); + jobsUIDsToRetry.insert(jobUID, jobDetail); + } + + // remove duplicate jobs (e.g., in the selected list there is multiple + // entries of the same job canceled/failed, we don't want running multiple + // times the same jobs) + + d->Scheduler->runJobs(jobsUIDsToRetry); + d->JobsView->clearSelection(); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::onResetFiltersButtonClicked() +{ + Q_D(ctkDICOMJobListWidget); + d->ShowCompletedButton->setChecked(false); + d->FilterLineEdit->setText(""); + d->FilterColumnComboBox->setCurrentIndex(0); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::onShowCompletedButtonToggled(bool toggled) +{ + Q_D(ctkDICOMJobListWidget); + QString text = toggled ? "" : tr("queued|in-progress|canceled|failed"); + d->showCompletedProxyModel->setFilterRegExp(text); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::onClearCompletedButtonClicked() +{ + Q_D(ctkDICOMJobListWidget); + d->dataModel->clearCompletedJobs(); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::onClearAllButtonClicked() +{ + Q_D(ctkDICOMJobListWidget); + d->Scheduler->stopAllJobs(true); + d->dataModel->removeRows(0, d->dataModel->rowCount()); +} diff --git a/Libs/DICOM/Widgets/ctkDICOMJobListWidget.h b/Libs/DICOM/Widgets/ctkDICOMJobListWidget.h new file mode 100644 index 0000000000..3f31f1a72e --- /dev/null +++ b/Libs/DICOM/Widgets/ctkDICOMJobListWidget.h @@ -0,0 +1,85 @@ +/*========================================================================= + + Library: CTK + + Copyright (c) Kitware Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Davide Punzo, punzodavide@hotmail.it, + and development was supported by the Center for Intelligent Image-guided Interventions (CI3). + +=========================================================================*/ + +#ifndef __ctkDICOMJobListWidget_h +#define __ctkDICOMJobListWidget_h + +#include "ctkDICOMWidgetsExport.h" + +// Qt includes +#include +#include + +class ctkDICOMJobListWidgetPrivate; +class ctkDICOMScheduler; + +/// \ingroup DICOM_Widgets +class CTK_DICOM_WIDGETS_EXPORT ctkDICOMJobListWidget : public QWidget +{ + Q_OBJECT; + +public: + typedef QWidget Superclass; + explicit ctkDICOMJobListWidget(QWidget* parent = nullptr); + virtual ~ctkDICOMJobListWidget(); + + /// Return the scheduler. + Q_INVOKABLE ctkDICOMScheduler* scheduler() const; + /// Return the scheduler as a shared pointer + /// (not Python-wrappable). + QSharedPointer schedulerShared() const; + /// Set the scheduler. + Q_INVOKABLE void setScheduler(ctkDICOMScheduler& scheduler); + /// Set the scheduler as a shared pointer + /// (not Python-wrappable). + void setScheduler(QSharedPointer scheduler); + +public Q_SLOTS: + void onJobQueued(QVariant); + void onJobStarted(QVariant); + void onJobCanceled(QVariant); + void onJobFailed(QVariant); + void onJobFinished(QVariant); + void onProgressJobDetail(QVariant); + + void onFilterTextChanged(QString); + void onFilterColumnChanged(QString); + + void onJobsViewSelectionChanged(); + void onSelectAllButtonClicked(); + void onStopButtonClicked(); + void onRetryButtonClicked(); + void onResetFiltersButtonClicked(); + void onShowCompletedButtonToggled(bool); + void onClearCompletedButtonClicked(); + void onClearAllButtonClicked(); + +protected: + QScopedPointer d_ptr; + +private: + Q_DECLARE_PRIVATE(ctkDICOMJobListWidget); + Q_DISABLE_COPY(ctkDICOMJobListWidget); +}; + +#endif diff --git a/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp index 81e7bc5c4f..ef41adda11 100644 --- a/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp @@ -37,15 +37,15 @@ // ctkDICOMCore includes #include "ctkDICOMDatabase.h" -#include "ctkDICOMScheduler.h" #include "ctkDICOMJobResponseSet.h" +#include "ctkDICOMScheduler.h" #include "ctkDICOMThumbnailGenerator.h" // ctkDICOMWidgets includes #include "ctkDICOMSeriesItemWidget.h" #include "ui_ctkDICOMSeriesItemWidget.h" -static ctkLogger logger("org.commontk.DICOM.Widgets.ctkDICOMSeriesItemWidget"); +static ctkLogger logger("org.commontk.DICOM.Widgets.DICOMSeriesItemWidget"); //---------------------------------------------------------------------------- class ctkDICOMSeriesItemWidgetPrivate : public Ui_ctkDICOMSeriesItemWidget @@ -219,6 +219,7 @@ void ctkDICOMSeriesItemWidgetPrivate::createThumbnail(ctkDICOMJobDetail td) QStringList urlsList = this->DicomDatabase->urlsForSeries(this->SeriesInstanceUID); filesList.removeAll(QString("")); int numberOfUrls = urlsList.count(); + bool renderThumbnail = false; if (!this->IsCloud && numberOfFrames > 0 && numberOfUrls > 0 && numberOfFiles < numberOfFrames) { this->IsCloud = true; @@ -226,6 +227,7 @@ void ctkDICOMSeriesItemWidgetPrivate::createThumbnail(ctkDICOMJobDetail td) } else if (this->IsCloud && numberOfFrames > 0 && numberOfFiles == numberOfFrames) { + renderThumbnail = true; this->IsCloud = false; this->SeriesThumbnail->operationProgressBar()->hide(); } @@ -304,19 +306,33 @@ void ctkDICOMSeriesItemWidgetPrivate::createThumbnail(ctkDICOMJobDetail td) (jobType == ctkDICOMJobResponseSet::JobType::None || jobType == ctkDICOMJobResponseSet::JobType::QueryInstances))) { - this->Scheduler->retrieveSeries(this->PatientID, - this->StudyInstanceUID, - this->SeriesInstanceUID, - this->RaiseJobsPriority ? QThread::HighestPriority : QThread::LowPriority); + QList> jobs = + this->Scheduler->getJobsByDICOMUIDs({}, + {}, + {this->SeriesInstanceUID}); + if (jobs.count() == 0) + { + this->Scheduler->retrieveSeries(this->PatientID, + this->StudyInstanceUID, + this->SeriesInstanceUID, + this->RaiseJobsPriority ? QThread::HighestPriority : QThread::LowPriority); + } } } file = this->DicomDatabase->fileForInstance(this->CentralFrameSOPInstanceUID); - if ((jobSopInstanceUID.isEmpty() || - jobSopInstanceUID == this->CentralFrameSOPInstanceUID || - jobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance || - jobType == ctkDICOMJobResponseSet::JobType::StoreSOPInstance) && - !file.isEmpty()) + if (file.isEmpty()) + { + return; + } + + + + if (jobSopInstanceUID.isEmpty() || + ((jobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance || + jobType == ctkDICOMJobResponseSet::JobType::StoreSOPInstance) && + jobSopInstanceUID == this->CentralFrameSOPInstanceUID) || + renderThumbnail) { this->drawThumbnail(file, numberOfFrames); } @@ -425,7 +441,11 @@ void ctkDICOMSeriesItemWidgetPrivate::drawThumbnail(const QString& file, int num Qt::AlignBottom | Qt::AlignLeft, bottomLeftString); QSvgRenderer renderer; - if (this->IsCloud) + if (this->RetrieveFailed) + { + renderer.load(QString(":Icons/error_red.svg")); + } + else if (this->IsCloud) { if (this->NumberOfDownloads > 0) { @@ -743,6 +763,14 @@ int ctkDICOMSeriesItemWidget::thumbnailSizePixel() const return d->ThumbnailSizePixel; } +//---------------------------------------------------------------------------- +void ctkDICOMSeriesItemWidget::resetOperationProgressBar() +{ + Q_D(ctkDICOMSeriesItemWidget); + d->NumberOfDownloads = 0; + d->SeriesThumbnail->setOperationProgress(0); +} + //---------------------------------------------------------------------------- static void skipDelete(QObject* obj) { diff --git a/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.h b/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.h index fadd154770..6a5e303c52 100644 --- a/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.h +++ b/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.h @@ -134,6 +134,9 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMSeriesItemWidget : public QWidget int thumbnailSizePixel() const; ///@} + /// Reset progress bar + Q_INVOKABLE void resetOperationProgressBar(); + /// Return the scheduler. Q_INVOKABLE ctkDICOMScheduler* scheduler() const; /// Return the scheduler as a shared pointer diff --git a/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.cpp b/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.cpp index 696c2e6d8b..3d4ae4b3dd 100644 --- a/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.cpp @@ -50,7 +50,7 @@ #include "ctkDICOMServerNodeWidget2.h" #include "ui_ctkDICOMServerNodeWidget2.h" -static ctkLogger logger("org.commontk.DICOM.Widgets.ctkDICOMServerNodeWidget2"); +static ctkLogger logger("org.commontk.DICOM.Widgets.DICOMServerNodeWidget2"); class QCenteredStyledItemDelegate : public QStyledItemDelegate { @@ -205,7 +205,8 @@ class ctkDICOMServerNodeWidget2Private : public Ui_ctkDICOMServerNodeWidget2 int addServerNode(const QMap& parameters); int addServerNode(ctkDICOMServer* server); QSharedPointer createServerFromServerNode(const QMap& node); - void updateProxyComboBoxes(const QString& connectionName, int rowCount) const; + void updateProxyComboBoxes() const; + QStringList getAllServerNames(); bool SettingsModified; QSharedPointer Scheduler; @@ -464,7 +465,7 @@ int ctkDICOMServerNodeWidget2Private::addServerNode(const QMapgetServerNodeRowFromConnectionName(node["Name"].toString()) != -1) { - logger.debug("addServerNode failed: the server has a duplicate. The connection name has to be unique \n"); + logger.warn("addServerNode failed: the server has a duplicate. The connection name has to be unique \n"); return -1; } @@ -555,8 +556,6 @@ int ctkDICOMServerNodeWidget2Private::addServerNode(const QMaponSettingsModified(); - this->updateProxyComboBoxes(serverName, rowCount); - return rowCount; } @@ -668,8 +667,6 @@ int ctkDICOMServerNodeWidget2Private::addServerNode(ctkDICOMServer* server) this->NodeTable->setCellWidget(rowCount, ctkDICOMServerNodeWidget2::ProxyColumn, proxyComboBox); this->NodeTable->setItem(rowCount, ctkDICOMServerNodeWidget2::ProxyColumn, newItem); - this->updateProxyComboBoxes(server->connectionName(), rowCount); - if (server->proxyServer()) { this->addServerNode(server->proxyServer()); @@ -701,25 +698,25 @@ QSharedPointer ctkDICOMServerNodeWidget2Private::createServerFro } //---------------------------------------------------------------------------- -void ctkDICOMServerNodeWidget2Private::updateProxyComboBoxes(const QString& connectionName, int rowCount) const +void ctkDICOMServerNodeWidget2Private::updateProxyComboBoxes() const { + int rowCount = this->NodeTable->rowCount(); + QStringList serverNames = this->getAllNodesName(); for (int row = 0; row < rowCount; ++row) { QComboBox* proxyComboBox = qobject_cast(this->NodeTable->cellWidget(row, ctkDICOMServerNodeWidget2::ProxyColumn)); if (proxyComboBox) { - QStringListModel* cbModel = qobject_cast(proxyComboBox->model()); - if (cbModel) - { - QStringList nodesNames = cbModel->stringList(); - if (nodesNames.contains(connectionName)) - { - continue; - } - } - proxyComboBox->addItem(connectionName); + bool wasBlocking = proxyComboBox->blockSignals(true); + + QString currentServer = proxyComboBox->currentText(); + proxyComboBox->clear(); + proxyComboBox->addItem(""); + proxyComboBox->addItems(serverNames); + proxyComboBox->setCurrentText(currentServer); + proxyComboBox->blockSignals(wasBlocking); } - } + } } //---------------------------------------------------------------------------- @@ -911,6 +908,8 @@ void ctkDICOMServerNodeWidget2::updateGUIState() { d->StorageStatusValueLabel->setText(QObject::tr("Inactive")); } + + d->updateProxyComboBoxes(); } //---------------------------------------------------------------------------- diff --git a/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp index 0ca136ba95..6d0aa6bda9 100644 --- a/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp @@ -41,7 +41,7 @@ #include -static ctkLogger logger("org.commontk.DICOM.Widgets.ctkDICOMStudyItemWidget"); +static ctkLogger logger("org.commontk.DICOM.Widgets.DICOMStudyItemWidget"); //---------------------------------------------------------------------------- static void skipDelete(QObject* obj) diff --git a/Libs/DICOM/Widgets/ctkDICOMThumbnailListWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMThumbnailListWidget.cpp index fe1a3f8249..018eb556ca 100644 --- a/Libs/DICOM/Widgets/ctkDICOMThumbnailListWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMThumbnailListWidget.cpp @@ -54,7 +54,7 @@ // DCMTK includes #include -static ctkLogger logger("org.commontk.DICOM.Widgets.ctkDICOMThumbnailListWidget"); +static ctkLogger logger("org.commontk.DICOM.Widgets.DICOMThumbnailListWidget"); Q_DECLARE_METATYPE(QPersistentModelIndex); diff --git a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp index c2533c97ac..fd5dbb5e09 100644 --- a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp @@ -58,7 +58,7 @@ #include "ctkDICOMVisualBrowserWidget.h" #include "ui_ctkDICOMVisualBrowserWidget.h" -static ctkLogger logger("org.commontk.DICOM.Widgets.ctkDICOMVisualBrowserWidget"); +static ctkLogger logger("org.commontk.DICOM.Widgets.DICOMVisualBrowserWidget"); class ctkDICOMMetadataDialog : public QDialog { @@ -291,12 +291,9 @@ ctkDICOMVisualBrowserWidgetPrivate::ctkDICOMVisualBrowserWidgetPrivate(ctkDICOMV this->IsGUIUpdating = false; this->IsLoading = false; - this->ServerNodeWidget = new ctkDICOMServerNodeWidget2(); - this->ServerNodeWidget->setScheduler(this->Scheduler); - this->connectScheduler(); - this->ExportProgress = nullptr; this->UpdateSchemaProgress = nullptr; + this->ServerNodeWidget = nullptr; } //---------------------------------------------------------------------------- @@ -320,6 +317,9 @@ void ctkDICOMVisualBrowserWidgetPrivate::init() q, SLOT(updateDatabase())); this->WarningPushButton->hide(); + QObject::connect(this->WarningPushButton, SIGNAL(clicked()), + q, SLOT(onWarningPushButtonClicked())); + QObject::connect(this->FilteringPatientIDSearchBox, SIGNAL(textChanged(QString)), q, SLOT(onFilteringPatientIDChanged())); @@ -342,7 +342,6 @@ void ctkDICOMVisualBrowserWidgetPrivate::init() QObject::connect(this->QueryPatientPushButton, SIGNAL(clicked()), q, SLOT(onQueryPatients())); - this->ServersSettingsCollapsibleGroupBox->layout()->addWidget(this->ServerNodeWidget); this->PatientsTabWidget->clear(); // setup patients menu @@ -401,6 +400,16 @@ void ctkDICOMVisualBrowserWidgetPrivate::init() q, SLOT(onImportDirectoryComboBoxCurrentIndexChanged(int))); this->ProgressFrame->hide(); + + this->ServersSettingsCollapsibleGroupBox->setCollapsed(true); + this->JobsCollapsibleGroupBox->setCollapsed(true); + + this->ServerNodeWidget = new ctkDICOMServerNodeWidget2(q); + this->ServerNodeWidget->setScheduler(this->Scheduler); + this->ServersSettingsCollapsibleGroupBox->layout()->addWidget(this->ServerNodeWidget); + + this->JobListWidget->setScheduler(this->Scheduler); + this->connectScheduler(); } //---------------------------------------------------------------------------- @@ -414,8 +423,8 @@ void ctkDICOMVisualBrowserWidgetPrivate::disconnectScheduler() ctkDICOMVisualBrowserWidget::disconnect(this->Scheduler.data(), SIGNAL(progressJobDetail(QVariant)), q, SLOT(updateGUIFromScheduler(QVariant))); - ctkDICOMVisualBrowserWidget::disconnect(this->Scheduler.data(), SIGNAL(taskFailed(QString, QString)), - q, SLOT(onTaskFailed(QString, QString))); + ctkDICOMVisualBrowserWidget::disconnect(this->Scheduler.data(), SIGNAL(jobFailed(QVariant)), + q, SLOT(onJobFailed(QVariant))); ctkDICOMVisualBrowserWidget::disconnect(this->Indexer.data(), SIGNAL(progress(int)), q, SLOT(onIndexingProgress(int))); ctkDICOMVisualBrowserWidget::disconnect(this->Indexer.data(), SIGNAL(progressStep(QString)), q, SLOT(onIndexingProgressStep(QString))); ctkDICOMVisualBrowserWidget::disconnect(this->Indexer.data(), SIGNAL(progressDetail(QString)), q, SLOT(onIndexingProgressDetail(QString))); @@ -433,8 +442,10 @@ void ctkDICOMVisualBrowserWidgetPrivate::connectScheduler() ctkDICOMVisualBrowserWidget::connect(this->Scheduler.data(), SIGNAL(progressJobDetail(QVariant)), q, SLOT(updateGUIFromScheduler(QVariant))); + ctkDICOMVisualBrowserWidget::connect(this->Scheduler.data(), SIGNAL(jobStarted(QVariant)), + q, SLOT(onJobStarted(QVariant))); ctkDICOMVisualBrowserWidget::connect(this->Scheduler.data(), SIGNAL(jobFailed(QVariant)), - q, SLOT(onTaskFailed(QVariant))); + q, SLOT(onJobFailed(QVariant))); ctkDICOMVisualBrowserWidget::connect(this->Indexer.data(), SIGNAL(progress(int)), q, SLOT(onIndexingProgress(int))); ctkDICOMVisualBrowserWidget::connect(this->Indexer.data(), SIGNAL(progressStep(QString)), q, SLOT(onIndexingProgressStep(QString))); ctkDICOMVisualBrowserWidget::connect(this->Indexer.data(), SIGNAL(progressDetail(QString)), q, SLOT(onIndexingProgressDetail(QString))); @@ -912,9 +923,9 @@ void ctkDICOMVisualBrowserWidgetPrivate::retrieveSeries() } } - this->Scheduler->stopJobsByUIDs({}, - {}, - seriesInstanceUIDsToStop); + this->Scheduler->stopJobsByDICOMUIDs({}, + {}, + seriesInstanceUIDsToStop); bool wait = true; while (wait) @@ -937,7 +948,6 @@ void ctkDICOMVisualBrowserWidgetPrivate::retrieveSeries() } this->updateFiltersWarnings(); - this->ProgressFrame->hide(); this->QueryPatientPushButton->setIcon(QIcon(":/Icons/query.svg")); foreach (ctkDICOMSeriesItemWidget* seriesItemWidget, seriesWidgetsList) @@ -1441,6 +1451,7 @@ void ctkDICOMVisualBrowserWidget::setScheduler(ctkDICOMScheduler& Scheduler) d->disconnectScheduler(); d->Scheduler = QSharedPointer(&Scheduler, skipDelete); d->ServerNodeWidget->setScheduler(d->Scheduler); + d->JobListWidget->setScheduler(d->Scheduler); d->connectScheduler(); } @@ -1451,6 +1462,7 @@ void ctkDICOMVisualBrowserWidget::setScheduler(QSharedPointer d->disconnectScheduler(); d->Scheduler = Scheduler; d->ServerNodeWidget->setScheduler(d->Scheduler); + d->JobListWidget->setScheduler(d->Scheduler); d->connectScheduler(); } @@ -1586,7 +1598,21 @@ int ctkDICOMVisualBrowserWidget::getServerIndexFromName(const QString& connectio } //------------------------------------------------------------------------------ -ctkDICOMServerNodeWidget2* ctkDICOMVisualBrowserWidget::serverSettingsWidget() +ctkDICOMJobListWidget *ctkDICOMVisualBrowserWidget::jobListWidget() +{ + Q_D(ctkDICOMVisualBrowserWidget); + return d->JobListWidget; +} + +//------------------------------------------------------------------------------ +ctkCollapsibleGroupBox *ctkDICOMVisualBrowserWidget::jobListGroupBox() +{ + Q_D(ctkDICOMVisualBrowserWidget); + return d->JobsCollapsibleGroupBox; +} + +//------------------------------------------------------------------------------ +ctkDICOMServerNodeWidget2 *ctkDICOMVisualBrowserWidget::serverSettingsWidget() { Q_D(ctkDICOMVisualBrowserWidget); return d->ServerNodeWidget; @@ -2223,6 +2249,14 @@ void ctkDICOMVisualBrowserWidget::updateDatabase() this->setDatabaseDirectory(dir); } +//------------------------------------------------------------------------------ +void ctkDICOMVisualBrowserWidget::onWarningPushButtonClicked() +{ + Q_D(ctkDICOMVisualBrowserWidget); + d->WarningPushButton->hide(); + d->JobsCollapsibleGroupBox->setChecked(true); +} + //------------------------------------------------------------------------------ QStringList ctkDICOMVisualBrowserWidget::fileListForCurrentSelection(ctkDICOMModel::IndexType level, const QList& selectedWidgets) @@ -2382,9 +2416,9 @@ void ctkDICOMVisualBrowserWidget::removeSelectedItems(ctkDICOMModel::IndexType l } // Stop fetching jobs for selected widgets. - d->Scheduler->stopJobsByUIDs(selectedPatientUIDs, - selectedStudyUIDs, - selectedSeriesUIDs); + d->Scheduler->stopJobsByDICOMUIDs({}, + {}, + selectedSeriesUIDs); foreach (const QString& uid, selectedSeriesUIDs) { @@ -2570,8 +2604,6 @@ void ctkDICOMVisualBrowserWidget::onQueryPatients() d->Scheduler->queryPatients(QThread::NormalPriority); d->QueryPatientPushButton->setIcon(QIcon(":/Icons/wait.svg")); - d->ProgressFrame->show(); - d->ProgressDetailLineEdit->hide(); } } @@ -2579,7 +2611,6 @@ void ctkDICOMVisualBrowserWidget::onQueryPatients() void ctkDICOMVisualBrowserWidget::updateGUIFromScheduler(const QVariant& data) { Q_D(ctkDICOMVisualBrowserWidget); - d->ProgressFrame->hide(); d->QueryPatientPushButton->setIcon(QIcon(":/Icons/query.svg")); ctkDICOMJobDetail td = data.value(); @@ -2610,7 +2641,25 @@ void ctkDICOMVisualBrowserWidget::updateGUIFromScheduler(const QVariant& data) } //------------------------------------------------------------------------------ -void ctkDICOMVisualBrowserWidget::onTaskFailed(const QVariant& data) +void ctkDICOMVisualBrowserWidget::onJobStarted(const QVariant &data) +{ + Q_D(ctkDICOMVisualBrowserWidget); + ctkDICOMJobDetail td = data.value(); + + if (td.JobClass == "ctkDICOMRetrieveJob") + { + ctkDICOMSeriesItemWidget* seriesItemWidget = + d->getCurrentPatientSeriesWidgetByUIDs(td.StudyInstanceUID, td.SeriesInstanceUID); + if (seriesItemWidget) + { + seriesItemWidget->setRetrieveFailed(false); + seriesItemWidget->resetOperationProgressBar(); + } + } +} + +//------------------------------------------------------------------------------ +void ctkDICOMVisualBrowserWidget::onJobFailed(const QVariant& data) { Q_D(ctkDICOMVisualBrowserWidget); ctkDICOMJobDetail td = data.value(); @@ -2618,7 +2667,6 @@ void ctkDICOMVisualBrowserWidget::onTaskFailed(const QVariant& data) if (td.JobClass == "ctkDICOMQueryJob") { d->updateFiltersWarnings(); - d->ProgressFrame->hide(); d->QueryPatientPushButton->setIcon(QIcon(":/Icons/query.svg")); } @@ -2630,11 +2678,12 @@ void ctkDICOMVisualBrowserWidget::onTaskFailed(const QVariant& data) { seriesItemWidget->setRetrieveFailed(true); } - - d->WarningPushButton->setText(tr("%1 job failed to fetch the data." - "\nFor more information please open the error report console. \n").arg(td.JobUID)); - d->WarningPushButton->show(); } + + QString job = td.JobClass.replace("ctkDICOM", "").replace("Job", ""); + d->WarningPushButton->setText(tr("%1 job failed." + "\nFor more information open the Jobs section. \n").arg(job)); + d->WarningPushButton->show(); } //------------------------------------------------------------------------------ diff --git a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.h b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.h index bcf8aaa70f..457a46a569 100644 --- a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.h +++ b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.h @@ -42,11 +42,12 @@ class ctkCollapsibleGroupBox; class ctkDICOMVisualBrowserWidgetPrivate; class ctkDICOMDatabase; -class ctkFileDialog; +class ctkDICOMJobListWidget; class ctkDICOMScheduler; class ctkDICOMServer; class ctkDICOMServerNodeWidget2; class ctkDICOMJobResponseSet; +class ctkFileDialog; /// \ingroup DICOM_Widgets /// @@ -161,6 +162,8 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMVisualBrowserWidget : public QWidget Q_INVOKABLE void removeAllServers(); Q_INVOKABLE QString getServerNameFromIndex(int id); Q_INVOKABLE int getServerIndexFromName(const QString& connectionName); + Q_INVOKABLE ctkDICOMJobListWidget* jobListWidget(); + Q_INVOKABLE ctkCollapsibleGroupBox* jobListGroupBox(); Q_INVOKABLE ctkDICOMServerNodeWidget2* serverSettingsWidget(); Q_INVOKABLE ctkCollapsibleGroupBox* serverSettingsGroupBox(); ///@} @@ -342,6 +345,7 @@ public Q_SLOTS: /// Update database in-place to required schema version void updateDatabase(); + void onWarningPushButtonClicked(); void onFilteringPatientIDChanged(); void onFilteringPatientNameChanged(); void onFilteringStudyDescriptionChanged(); @@ -351,7 +355,8 @@ public Q_SLOTS: void onQueryPatients(); void onShowPatients(); void updateGUIFromScheduler(const QVariant&); - void onTaskFailed(const QVariant&); + void onJobStarted(const QVariant&); + void onJobFailed(const QVariant&); void onPatientItemChanged(int); void onClose(); void onLoad();