diff --git a/Libs/Core/ctkAbstractJob.cpp b/Libs/Core/ctkAbstractJob.cpp index fc9f923550..feff3ab5cc 100644 --- a/Libs/Core/ctkAbstractJob.cpp +++ b/Libs/Core/ctkAbstractJob.cpp @@ -38,6 +38,7 @@ ctkAbstractJob::ctkAbstractJob() this->MaximumConcurrentJobsPerType = 20; this->Priority = QThread::Priority::LowPriority; this->CreationDateTime = QDateTime::currentDateTime(); + this->DestroyAfterUse = false; } //---------------------------------------------------------------------------- @@ -218,7 +219,7 @@ QString ctkAbstractJob::loggedText() const } //---------------------------------------------------------------------------- -void ctkAbstractJob::setLoggedText(QString loggedText) +void ctkAbstractJob::addLoggedText(QString loggedText) { if (loggedText.isEmpty()) { @@ -228,6 +229,18 @@ void ctkAbstractJob::setLoggedText(QString loggedText) this->LoggedText += loggedText; } +//---------------------------------------------------------------------------- +bool ctkAbstractJob::destroyAfterUse() const +{ + return this->DestroyAfterUse; +} + +//---------------------------------------------------------------------------- +void ctkAbstractJob::setDestroyAfterUse(bool destroyAfterUse) +{ + this->DestroyAfterUse = destroyAfterUse; +} + //---------------------------------------------------------------------------- QVariant ctkAbstractJob::toVariant() { diff --git a/Libs/Core/ctkAbstractJob.h b/Libs/Core/ctkAbstractJob.h index 70cce73b8a..7ce7b70a79 100644 --- a/Libs/Core/ctkAbstractJob.h +++ b/Libs/Core/ctkAbstractJob.h @@ -55,7 +55,8 @@ class CTK_CORE_EXPORT ctkAbstractJob : public QObject Q_PROPERTY(QDateTime startDateTime READ startDateTime); Q_PROPERTY(QDateTime completionDateTime READ completionDateTime); Q_PROPERTY(QString runningThreadID READ runningThreadID WRITE setRunningThreadID); - Q_PROPERTY(QString loggedText READ loggedText WRITE setLoggedText); + Q_PROPERTY(QString loggedText READ loggedText WRITE addLoggedText); + Q_PROPERTY(bool destroyAfterUse READ destroyAfterUse WRITE setDestroyAfterUse); public: explicit ctkAbstractJob(); @@ -158,7 +159,7 @@ class CTK_CORE_EXPORT ctkAbstractJob : public QObject ///@{ /// Logged Text QString loggedText() const; - void setLoggedText(QString loggedText); + void addLoggedText(QString loggedText); ///@} /// Generate worker for job @@ -168,7 +169,7 @@ class CTK_CORE_EXPORT ctkAbstractJob : public QObject Q_INVOKABLE virtual ctkAbstractJob* clone() const = 0; /// Logger report string formatting for specific job - Q_INVOKABLE virtual QString loggerReport(const QString& status) const = 0; + Q_INVOKABLE virtual QString loggerReport(const QString& status) = 0; /// Return the QVariant value of this job. /// @@ -177,6 +178,16 @@ class CTK_CORE_EXPORT ctkAbstractJob : public QObject /// \sa ctkJobDetail Q_INVOKABLE virtual QVariant toVariant(); + /// Free used resources from job after worker is done + Q_INVOKABLE virtual void freeUsedResources() = 0; + + ///@{ + /// Destroy job pointer after worker is done + /// default: false + bool destroyAfterUse() const; + void setDestroyAfterUse(bool destroyAfterUse); + ///@} + Q_SIGNALS: void started(); void userStopped(); @@ -198,6 +209,7 @@ class CTK_CORE_EXPORT ctkAbstractJob : public QObject QDateTime CompletionDateTime; QString RunningThreadID; QString LoggedText; + bool DestroyAfterUse; private: Q_DISABLE_COPY(ctkAbstractJob) diff --git a/Libs/Core/ctkCorePythonQtDecorators.h b/Libs/Core/ctkCorePythonQtDecorators.h index 7ca46eaa8c..94224dc5d6 100644 --- a/Libs/Core/ctkCorePythonQtDecorators.h +++ b/Libs/Core/ctkCorePythonQtDecorators.h @@ -260,6 +260,51 @@ public Q_SLOTS: { return td->JobUID; } + void setCreationDateTime(ctkJobDetail* td, QString creationDateTime) + { + td->CreationDateTime = creationDateTime; + } + + QString creationDateTime(ctkJobDetail* td) + { + return td->CreationDateTime; + } + + void setStartDateTime(ctkJobDetail* td, QString startDateTime) + { + td->StartDateTime = startDateTime; + } + QString startDateTime(ctkJobDetail* td) + { + return td->StartDateTime; + } + + void setCompletionDateTime(ctkJobDetail* td, QString completionDateTime) + { + td->CompletionDateTime = completionDateTime; + } + QString completionDateTime(ctkJobDetail* td) + { + return td->CompletionDateTime; + } + + void setRunningThreadID(ctkJobDetail* td, QString runningThreadID) + { + td->RunningThreadID = runningThreadID; + } + QString runningThreadID(ctkJobDetail* td) + { + return td->RunningThreadID; + } + + void setLogging(ctkJobDetail* td, QString logging) + { + td->Logging = logging; + } + QString logging(ctkJobDetail* td) + { + return td->Logging; + } }; //----------------------------------------------------------------------------- diff --git a/Libs/Core/ctkJobScheduler.cpp b/Libs/Core/ctkJobScheduler.cpp index e6ba99488f..7ecdbdb3c9 100644 --- a/Libs/Core/ctkJobScheduler.cpp +++ b/Libs/Core/ctkJobScheduler.cpp @@ -54,8 +54,6 @@ ctkJobSchedulerPrivate::~ctkJobSchedulerPrivate() = default; void ctkJobSchedulerPrivate::init() { Q_Q(ctkJobScheduler); - QObject::connect(this, SIGNAL(queueJobsInThreadPool()), - this, SLOT(onQueueJobsInThreadPool())); this->ThreadPool = QSharedPointer(new QThreadPool(this)); this->ThreadPool->setMaxThreadCount(20); @@ -81,7 +79,6 @@ void ctkJobSchedulerPrivate::onQueueJobsInThreadPool() // prevent conflicts with other QMutexLockers within the scheduler's methods. QMutexLocker locker(&this->QueueMutex); - foreach (QThread::Priority priority, (QList() << QThread::Priority::HighestPriority << QThread::Priority::HighPriority @@ -193,15 +190,13 @@ bool ctkJobSchedulerPrivate::insertJob(QSharedPointer job) } emit q->jobInitialized(job->toVariant()); - emit this->queueJobsInThreadPool(); + this->onQueueJobsInThreadPool(); return true; } //------------------------------------------------------------------------------ -bool ctkJobSchedulerPrivate::removeJob(const QString& jobUID) +bool ctkJobSchedulerPrivate::cleanJob(const QString &jobUID) { - Q_Q(ctkJobScheduler); - logger.debug(QString("ctkJobScheduler: deleting job object %1 in thread %2.\n") .arg(jobUID) .arg(QString::number(reinterpret_cast(QThread::currentThreadId()), 16))); @@ -216,25 +211,15 @@ bool ctkJobSchedulerPrivate::removeJob(const QString& jobUID) return false; } - QMap connections = this->JobsConnections.value(jobUID); - QObject::disconnect(connections.value("started")); - QObject::disconnect(connections.value("userStopped")); - QObject::disconnect(connections.value("finished")); - QObject::disconnect(connections.value("attemptFailed")); - QObject::disconnect(connections.value("failed")); - QObject::disconnect(connections.value("progress")); - - this->JobsConnections.remove(jobUID); - this->JobsQueue.remove(jobUID); + job->freeUsedResources(); } - emit this->queueJobsInThreadPool(); - + this->onQueueJobsInThreadPool(); return true; } //------------------------------------------------------------------------------ -void ctkJobSchedulerPrivate::removeJobs(const QStringList &jobUIDs) +void ctkJobSchedulerPrivate::cleanJobs(const QStringList &jobUIDs) { Q_Q(ctkJobScheduler); @@ -247,23 +232,13 @@ void ctkJobSchedulerPrivate::removeJobs(const QStringList &jobUIDs) foreach (QString jobUID, jobUIDs) { QSharedPointer job = this->JobsQueue.value(jobUID); - if (!job || !this->JobsConnections.contains(jobUID)) + if (!job) { continue; } datas.append(job->toVariant()); - - QMap connections = this->JobsConnections.value(jobUID); - QObject::disconnect(connections.value("started")); - QObject::disconnect(connections.value("userStopped")); - QObject::disconnect(connections.value("finished")); - QObject::disconnect(connections.value("attemptFailed")); - QObject::disconnect(connections.value("failed")); - QObject::disconnect(connections.value("progress")); - - this->JobsConnections.remove(jobUID); - this->JobsQueue.remove(jobUID); + job->freeUsedResources(); } } @@ -271,27 +246,57 @@ void ctkJobSchedulerPrivate::removeJobs(const QStringList &jobUIDs) } //------------------------------------------------------------------------------ -void ctkJobSchedulerPrivate::removeAllJobs() +bool ctkJobSchedulerPrivate::removeJob(const QString& jobUID) { - Q_Q(ctkJobScheduler); + logger.debug(QString("ctkJobScheduler: deleting job object %1 in thread %2.\n") + .arg(jobUID) + .arg(QString::number(reinterpret_cast(QThread::currentThreadId()), 16))); { // The QMutexLocker is enclosed within brackets to restrict its scope and // prevent conflicts with other QMutexLockers within the scheduler's methods. QMutexLocker locker(&this->QueueMutex); - foreach (QSharedPointer job, this->JobsQueue) + QSharedPointer job = this->JobsQueue.value(jobUID); + if (!job || !this->JobsConnections.contains(jobUID)) { - if (!job) - { - continue; - } + return false; + } - QString jobUID = job->jobUID(); - if (!this->JobsConnections.contains(jobUID)) + QMap connections = this->JobsConnections.value(jobUID); + QObject::disconnect(connections.value("started")); + QObject::disconnect(connections.value("userStopped")); + QObject::disconnect(connections.value("finished")); + QObject::disconnect(connections.value("attemptFailed")); + QObject::disconnect(connections.value("failed")); + QObject::disconnect(connections.value("progress")); + + this->JobsConnections.remove(jobUID); + this->JobsQueue.remove(jobUID); + } + + this->onQueueJobsInThreadPool(); + return true; +} + +//------------------------------------------------------------------------------ +void ctkJobSchedulerPrivate::removeJobs(const QStringList &jobUIDs) +{ + QList datas; + { + // The QMutexLocker is enclosed within brackets to restrict its scope and + // prevent conflicts with other QMutexLockers within the scheduler's methods. + QMutexLocker locker(&this->QueueMutex); + + foreach (QString jobUID, jobUIDs) + { + QSharedPointer job = this->JobsQueue.value(jobUID); + if (!job || !this->JobsConnections.contains(jobUID)) { continue; } + datas.append(job->toVariant()); + QMap connections = this->JobsConnections.value(jobUID); QObject::disconnect(connections.value("started")); QObject::disconnect(connections.value("userStopped")); @@ -453,6 +458,13 @@ void ctkJobScheduler::addJob(ctkAbstractJob* job) d->insertJob(jobShared); } +//---------------------------------------------------------------------------- +void ctkJobScheduler::resetJob(const QString &jobUID) +{ + Q_D(ctkJobScheduler); + d->cleanJob(jobUID); +} + //---------------------------------------------------------------------------- void ctkJobScheduler::deleteJob(const QString& jobUID) { @@ -460,6 +472,13 @@ void ctkJobScheduler::deleteJob(const QString& jobUID) d->removeJob(jobUID); } +//---------------------------------------------------------------------------- +void ctkJobScheduler::deleteJobs(const QStringList &jobUIDs) +{ + Q_D(ctkJobScheduler); + d->removeJobs(jobUIDs); +} + //---------------------------------------------------------------------------- void ctkJobScheduler::deleteWorker(const QString& jobUID) { @@ -540,11 +559,11 @@ void ctkJobScheduler::waitForDone(int msec) } //---------------------------------------------------------------------------- -void ctkJobScheduler::stopAllJobs(bool stopPersistentJobs) +QStringList ctkJobScheduler::stopAllJobs(bool stopPersistentJobs, bool removeJobs) { Q_D(ctkJobScheduler); - QStringList initializedStoppedJobsUIDs; + QStringList stoppedJobsUIDs; { // The QMutexLocker is enclosed within brackets to restrict its scope and // prevent conflicts with other QMutexLockers within the scheduler's methods. @@ -578,11 +597,18 @@ void ctkJobScheduler::stopAllJobs(bool stopPersistentJobs) QMap connections = d->JobsConnections.value(jobUID); QObject::disconnect(connections.value("userStopped")); job->setStatus(ctkAbstractJob::JobStatus::UserStopped); - initializedStoppedJobsUIDs.append(jobUID); + stoppedJobsUIDs.append(jobUID); } } - d->removeJobs(initializedStoppedJobsUIDs); + if (removeJobs) + { + d->removeJobs(stoppedJobsUIDs); + } + else + { + d->cleanJobs(stoppedJobsUIDs); + } // Stops queued and running jobs foreach (QSharedPointer worker, d->Workers) @@ -592,13 +618,16 @@ void ctkJobScheduler::stopAllJobs(bool stopPersistentJobs) { continue; } - + stoppedJobsUIDs.append(job->jobUID()); + job->setDestroyAfterUse(removeJobs); worker->requestCancel(); } + + return stoppedJobsUIDs; } //---------------------------------------------------------------------------- -void ctkJobScheduler::stopJobsByJobUIDs(const QStringList &jobUIDs) +void ctkJobScheduler::stopJobsByJobUIDs(const QStringList &jobUIDs, bool removeJobs) { Q_D(ctkJobScheduler); @@ -646,7 +675,14 @@ void ctkJobScheduler::stopJobsByJobUIDs(const QStringList &jobUIDs) } } - d->removeJobs(initializedStoppedJobsUIDs); + if (removeJobs) + { + d->removeJobs(initializedStoppedJobsUIDs); + } + else + { + d->cleanJobs(initializedStoppedJobsUIDs); + } // Stops running jobs foreach (QSharedPointer worker, d->Workers) @@ -659,11 +695,44 @@ void ctkJobScheduler::stopJobsByJobUIDs(const QStringList &jobUIDs) if ((!job->jobUID().isEmpty() && jobUIDs.contains(job->jobUID()))) { + job->setDestroyAfterUse(removeJobs); worker->requestCancel(); } } } +//---------------------------------------------------------------------------- +bool ctkJobScheduler::retryJob(const QString &jobUID) +{ + Q_D(ctkJobScheduler); + + QSharedPointer job = this->getJobSharedByUID(jobUID); + if (!job) + { + return false; + } + + if (job->status() < ctkAbstractJob::JobStatus::UserStopped && + job->status() > ctkAbstractJob::JobStatus::Finished) + { + return false; + } + + job->setStatus(ctkAbstractJob::JobStatus::Initialized); + emit this->jobInitialized(job->toVariant()); + d->onQueueJobsInThreadPool(); + return true; +} + +//---------------------------------------------------------------------------- +void ctkJobScheduler::retryJobs(const QStringList &jobUIDs) +{ + foreach (QString jobUID, jobUIDs) + { + this->retryJob(jobUID); + } +} + //---------------------------------------------------------------------------- int ctkJobScheduler::maximumThreadCount() const @@ -725,7 +794,14 @@ void ctkJobScheduler::onJobUserStopped(ctkAbstractJob* job) QVariant data = job->toVariant(); QString jobUID = job->jobUID(); this->deleteWorker(jobUID); - this->deleteJob(jobUID); + if (job->destroyAfterUse()) + { + this->deleteJob(jobUID); + } + else + { + this->resetJob(jobUID); + } d->BatchedJobsUserStopped.append(job->toVariant()); if (!d->ThrottleTimer->isActive()) @@ -748,7 +824,14 @@ void ctkJobScheduler::onJobFinished(ctkAbstractJob* job) QVariant data = job->toVariant(); QString jobUID = job->jobUID(); this->deleteWorker(jobUID); - this->deleteJob(jobUID); + if (job->destroyAfterUse()) + { + this->deleteJob(jobUID); + } + else + { + this->resetJob(jobUID); + } d->BatchedJobsFinished.append(job->toVariant()); if (!d->ThrottleTimer->isActive()) @@ -771,7 +854,14 @@ void ctkJobScheduler::onJobAttemptFailed(ctkAbstractJob* job) QVariant data = job->toVariant(); QString jobUID = job->jobUID(); this->deleteWorker(jobUID); - this->deleteJob(jobUID); + if (job->destroyAfterUse()) + { + this->deleteJob(jobUID); + } + else + { + this->resetJob(jobUID); + } d->BatchedJobsAttemptFailed.append(job->toVariant()); if (!d->ThrottleTimer->isActive()) @@ -794,7 +884,14 @@ void ctkJobScheduler::onJobFailed(ctkAbstractJob* job) QVariant data = job->toVariant(); QString jobUID = job->jobUID(); this->deleteWorker(jobUID); - this->deleteJob(jobUID); + if (job->destroyAfterUse()) + { + this->deleteJob(jobUID); + } + else + { + this->resetJob(jobUID); + } d->BatchedJobsFailed.append(job->toVariant()); if (!d->ThrottleTimer->isActive()) diff --git a/Libs/Core/ctkJobScheduler.h b/Libs/Core/ctkJobScheduler.h index 4ff945c054..cbe20c629d 100644 --- a/Libs/Core/ctkJobScheduler.h +++ b/Libs/Core/ctkJobScheduler.h @@ -59,15 +59,19 @@ class CTK_CORE_EXPORT ctkJobScheduler : public QObject int numberOfPersistentJobs(); int numberOfRunningJobs(); Q_INVOKABLE void addJob(ctkAbstractJob* job); + Q_INVOKABLE virtual void resetJob(const QString& jobUID); Q_INVOKABLE virtual void deleteJob(const QString& jobUID); + Q_INVOKABLE virtual void deleteJobs(const QStringList& jobUIDs); Q_INVOKABLE virtual void deleteWorker(const QString& jobUID); QSharedPointer getJobSharedByUID(const QString& jobUID); Q_INVOKABLE ctkAbstractJob* getJobByUID(const QString& jobUID); Q_INVOKABLE void waitForFinish(bool waitForPersistentJobs = false, bool processEvents = false); Q_INVOKABLE void waitForDone(int msec = -1); - Q_INVOKABLE void stopAllJobs(bool stopPersistentJobs = false); - Q_INVOKABLE void stopJobsByJobUIDs(const QStringList& jobUIDs); + Q_INVOKABLE QStringList stopAllJobs(bool stopPersistentJobs = false, bool removeJobs = true); + Q_INVOKABLE void stopJobsByJobUIDs(const QStringList& jobUIDs, bool removeJobs = false); + Q_INVOKABLE bool retryJob(const QString& jobUID); + Q_INVOKABLE void retryJobs(const QStringList& jobUIDs); ///@} ///@{ diff --git a/Libs/Core/ctkJobScheduler_p.h b/Libs/Core/ctkJobScheduler_p.h index f6a792abac..66fe453d53 100644 --- a/Libs/Core/ctkJobScheduler_p.h +++ b/Libs/Core/ctkJobScheduler_p.h @@ -44,9 +44,6 @@ class CTK_CORE_EXPORT ctkJobSchedulerPrivate : public QObject protected: ctkJobScheduler* const q_ptr; -Q_SIGNALS: - void queueJobsInThreadPool(); - public Q_SLOTS: virtual void onQueueJobsInThreadPool(); @@ -58,9 +55,10 @@ public Q_SLOTS: virtual void init(); virtual bool insertJob(QSharedPointer job); + virtual bool cleanJob(const QString& jobUID); + virtual void cleanJobs(const QStringList& jobUIDs); virtual bool removeJob(const QString& jobUID); virtual void removeJobs(const QStringList& jobUIDs); - virtual void removeAllJobs(); int getSameTypeJobsInThreadPoolQueueOrRunning(QSharedPointer job); QString generateUniqueJobUID(); void clearBactchedJobsLists(); diff --git a/Libs/DICOM/Core/CMakeLists.txt b/Libs/DICOM/Core/CMakeLists.txt index 5a67ae3b48..7ac2f12981 100644 --- a/Libs/DICOM/Core/CMakeLists.txt +++ b/Libs/DICOM/Core/CMakeLists.txt @@ -77,6 +77,14 @@ set(KIT_SRCS ctkDICOMStorageListenerWorker_p.h ctkDICOMTester.cpp ctkDICOMTester.h + ctkDICOMThumbnailGenerator.cpp + ctkDICOMThumbnailGenerator.h + ctkDICOMThumbnailGeneratorJob.cpp + ctkDICOMThumbnailGeneratorJob.h + ctkDICOMThumbnailGeneratorJob_p.h + ctkDICOMThumbnailGeneratorWorker.cpp + ctkDICOMThumbnailGeneratorWorker.h + ctkDICOMThumbnailGeneratorWorker_p.h ctkDICOMUtil.cpp ctkDICOMUtil.h ctkDICOMDisplayedFieldGeneratorRuleFactory.h @@ -144,6 +152,11 @@ set(KIT_MOC_SRCS ctkDICOMStorageListenerWorker.h ctkDICOMStorageListenerWorker_p.h ctkDICOMTester.h + ctkDICOMThumbnailGenerator.h + ctkDICOMThumbnailGeneratorJob.h + ctkDICOMThumbnailGeneratorJob_p.h + ctkDICOMThumbnailGeneratorWorker.h + ctkDICOMThumbnailGeneratorWorker_p.h ) # UI files @@ -159,7 +172,7 @@ set(KIT_resources # The following macro will read the target libraries from the file 'target_libraries.cmake' ctkFunctionGetTargetLibraries(KIT_target_libraries) -list(APPEND KIT_target_libraries Qt${CTK_QT_VERSION}::Sql) +list(APPEND KIT_target_libraries Qt${CTK_QT_VERSION}::Sql Qt${CTK_QT_VERSION}::Svg) # create a dcm query/retrieve service config file that points to the build dir set (DCMQRSCP_STORE_DIR ${CMAKE_CURRENT_BINARY_DIR}/Testing) diff --git a/Libs/DICOM/Core/ctkDICOMAbstractThumbnailGenerator.h b/Libs/DICOM/Core/ctkDICOMAbstractThumbnailGenerator.h index dd3e6d30e3..ae27a6e2ef 100644 --- a/Libs/DICOM/Core/ctkDICOMAbstractThumbnailGenerator.h +++ b/Libs/DICOM/Core/ctkDICOMAbstractThumbnailGenerator.h @@ -24,7 +24,7 @@ // Qt includes #include -#include +#include #include "ctkDICOMCoreExport.h" @@ -45,9 +45,9 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMAbstractThumbnailGenerator : public QObject virtual ~ctkDICOMAbstractThumbnailGenerator(); virtual bool generateThumbnail(DicomImage* dcmImage, const QString& path, - QVector color = QVector{169, 169, 169}) = 0; + QColor backgroundColor = Qt::darkGray) = 0; virtual void generateDocumentThumbnail(const QString &thumbnailPath, - QVector color = QVector{169, 169, 169}) = 0; + QColor backgroundColor = Qt::darkGray) = 0; protected: QScopedPointer d_ptr; diff --git a/Libs/DICOM/Core/ctkDICOMCorePythonQtDecorators.h b/Libs/DICOM/Core/ctkDICOMCorePythonQtDecorators.h index a0fe2f9471..b32ff144d1 100644 --- a/Libs/DICOM/Core/ctkDICOMCorePythonQtDecorators.h +++ b/Libs/DICOM/Core/ctkDICOMCorePythonQtDecorators.h @@ -78,33 +78,6 @@ public slots: return td->JobUID; } - void setCreationDateTime(ctkDICOMJobDetail* td, QString creationDateTime) - { - td->CreationDateTime = creationDateTime; - } - QString creationDateTime(ctkDICOMJobDetail* td) - { - return td->CreationDateTime; - } - - void setStartDateTime(ctkDICOMJobDetail* td, QString startDateTime) - { - td->StartDateTime = startDateTime; - } - QString startDateTime(ctkDICOMJobDetail* td) - { - return td->StartDateTime; - } - - void setCompletionDateTime(ctkDICOMJobDetail* td, QString completionDateTime) - { - td->CompletionDateTime = completionDateTime; - } - QString completionDateTime(ctkDICOMJobDetail* td) - { - return td->CompletionDateTime; - } - void setPatientID(ctkDICOMJobDetail* td, const QString& patientID) { td->PatientID = patientID; @@ -150,6 +123,42 @@ public slots: return td->ReferenceInserterJobUID; } + void setQueriedPatientIDs(ctkDICOMJobDetail* td, const QStringList& queriedPatientIDs) + { + td->QueriedPatientIDs = queriedPatientIDs; + } + QStringList queriedPatientIDs(ctkDICOMJobDetail* td) + { + return td->QueriedPatientIDs; + } + + void setQueriedStudyInstanceUIDs(ctkDICOMJobDetail* td, const QStringList& queriedStudyInstanceUIDs) + { + td->QueriedStudyInstanceUIDs = queriedStudyInstanceUIDs; + } + QStringList queriedStudyInstanceUIDs(ctkDICOMJobDetail* td) + { + return td->QueriedStudyInstanceUIDs; + } + + void setQueriedSeriesInstanceUIDs(ctkDICOMJobDetail* td, const QStringList& queriedSeriesInstanceUIDs) + { + td->QueriedSeriesInstanceUIDs = queriedSeriesInstanceUIDs; + } + QStringList queriedSeriesInstanceUIDs(ctkDICOMJobDetail* td) + { + return td->QueriedSeriesInstanceUIDs; + } + + void setQueriedSOPInstanceUIDs(ctkDICOMJobDetail* td, const QStringList& queriedSOPInstanceUIDs) + { + td->QueriedSOPInstanceUIDs = queriedSOPInstanceUIDs; + } + QStringList queriedSOPInstanceUIDs(ctkDICOMJobDetail* td) + { + return td->QueriedSOPInstanceUIDs; + } + void setConnectionName(ctkDICOMJobDetail* td, const QString& connectionName) { td->ConnectionName = connectionName; diff --git a/Libs/DICOM/Core/ctkDICOMDatabase.cpp b/Libs/DICOM/Core/ctkDICOMDatabase.cpp index 725a69b066..186428af95 100644 --- a/Libs/DICOM/Core/ctkDICOMDatabase.cpp +++ b/Libs/DICOM/Core/ctkDICOMDatabase.cpp @@ -2374,7 +2374,7 @@ bool ctkDICOMDatabase::storeThumbnailFile(const QString &originalFilePath, const QString &seriesInstanceUID, const QString &sopInstanceUID, const QString& modality, - QVector color) + QColor backgroundColor) { Q_D(ctkDICOMDatabase); if (!d->ThumbnailGenerator) @@ -2403,13 +2403,13 @@ bool ctkDICOMDatabase::storeThumbnailFile(const QString &originalFilePath, // NOTE: currently SEG objects are not fully supported by ctkDICOMThumbnailGenerator, // The rendering will fail and in addition SEG object can be very large and // loading the file can be slow. For now, we will just create a blank thumbnail with a document svg. - d->ThumbnailGenerator->generateDocumentThumbnail(thumbnailPath, color); + d->ThumbnailGenerator->generateDocumentThumbnail(thumbnailPath, backgroundColor); return true; } else { DicomImage dcmImage(QDir::toNativeSeparators(originalFilePath).toUtf8()); - return d->ThumbnailGenerator->generateThumbnail(&dcmImage, thumbnailPath, color); + return d->ThumbnailGenerator->generateThumbnail(&dcmImage, thumbnailPath, backgroundColor); } } @@ -2901,7 +2901,7 @@ void ctkDICOMDatabase::insert(const QList& ind } //------------------------------------------------------------------------------ -void ctkDICOMDatabase::insert(QList> jobResponseSets) +void ctkDICOMDatabase::insert(const QList& jobResponseSets) { Q_D(ctkDICOMDatabase); @@ -2910,7 +2910,7 @@ void ctkDICOMDatabase::insert(QList> jobR d->TagCacheDatabase.transaction(); d->Database.transaction(); QDir databaseDirectory(this->databaseDirectory()); - foreach (QSharedPointer jobResponseSet, jobResponseSets) + foreach (ctkDICOMJobResponseSet* jobResponseSet, jobResponseSets) { ctkDICOMJobResponseSet::JobType jobType = jobResponseSet->jobType(); QString filePath = jobResponseSet->filePath(); diff --git a/Libs/DICOM/Core/ctkDICOMDatabase.h b/Libs/DICOM/Core/ctkDICOMDatabase.h index d074967f3d..82365fc314 100644 --- a/Libs/DICOM/Core/ctkDICOMDatabase.h +++ b/Libs/DICOM/Core/ctkDICOMDatabase.h @@ -22,6 +22,7 @@ #define __ctkDICOMDatabase_h // Qt includes +#include #include #include #include @@ -216,7 +217,7 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMDatabase : public QObject const QString& seriesInstanceUID, const QString& sopInstanceUID, const QString& modality = "", - QVector color = QVector{169, 169, 169}); + QColor backgroundColor = Qt::darkGray); Q_INVOKABLE int patientsCount(); Q_INVOKABLE int studiesCount(); @@ -280,7 +281,7 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMDatabase : public QObject bool createHierarchy = true, const QString& destinationDirectoryName = QString()); Q_INVOKABLE void insert(const QList& indexingResults); - Q_INVOKABLE void insert(QList> jobResponseSets); + Q_INVOKABLE void insert(const QList& jobResponseSets); /// When a DICOM file is stored in the database (insert is called with storeFile=true) then /// path is constructed from study, series, and SOP instance UID. diff --git a/Libs/DICOM/Core/ctkDICOMEcho.cpp b/Libs/DICOM/Core/ctkDICOMEcho.cpp index c51acb0aff..14ba5081c9 100644 --- a/Libs/DICOM/Core/ctkDICOMEcho.cpp +++ b/Libs/DICOM/Core/ctkDICOMEcho.cpp @@ -19,7 +19,6 @@ =========================================================================*/ // Qt includes -#include #include #include #include @@ -27,7 +26,6 @@ // ctkDICOMCore includes #include "ctkDICOMEcho.h" -#include "ctkLogger.h" // DCMTK includes #include @@ -35,7 +33,7 @@ #include /* for class OFStandard */ #include -static ctkLogger logger("org.commontk.dicom.DICOMEcho"); +dcmtk::log4cplus::Logger rootLogEcho = dcmtk::log4cplus::Logger::getRoot(); //------------------------------------------------------------------------------ class ctkDICOMEchoPrivate @@ -75,7 +73,6 @@ ctkDICOMEchoPrivate::ctkDICOMEchoPrivate() this->JobUID = ""; this->Port = 80; - logger.debug("Setting Transfer Syntaxes"); OFList transferSyntaxes; transferSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax); transferSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax); @@ -191,15 +188,18 @@ bool ctkDICOMEcho::echo() if (!d->SCU->initNetwork().good()) { - logger.error("Error initializing the network"); + QString error = ctkDICOMEcho::tr("Error initializing the network"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogEcho, error.toStdString().c_str()); return false; } - logger.debug("Negotiating Association"); + QString debug = ctkDICOMEcho::tr("Negotiating Association"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogEcho, debug.toStdString().c_str()); OFCondition result = d->SCU->negotiateAssociation(); if (result.bad()) { - logger.error("Error negotiating the association: " + QString(result.text())); + QString error = ctkDICOMEcho::tr("Error negotiating the association: ") + QString(result.text()); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogEcho, error.toStdString().c_str()); return false; } @@ -208,17 +208,20 @@ bool ctkDICOMEcho::echo() "" /* don't care about transfer syntax */); if (d->PresentationContext == 0) { - logger.error ( "ECHO Request failed: No valid verification Presentation Context available" ); + QString error = ctkDICOMEcho::tr("ECHO Request failed: No valid verification Presentation Context available"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogEcho, error.toStdString().c_str()); d->releaseAssociation(); return false; } - logger.info("Seding Echo"); + QString info = ctkDICOMEcho::tr("Seding Echo"); + DCMTK_LOG4CPLUS_INFO_STR(rootLogEcho, info.toStdString().c_str()); // Issue ECHO request and let scu find presentation context itself (0) OFCondition status = d->SCU->sendECHORequest(d->PresentationContext); if (!status.good()) { - logger.error("Echo failed"); + QString error = ctkDICOMEcho::tr("Echo failed"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogEcho, error.toStdString().c_str()); d->releaseAssociation(); return false; } diff --git a/Libs/DICOM/Core/ctkDICOMEchoJob.cpp b/Libs/DICOM/Core/ctkDICOMEchoJob.cpp index d3d945a0ea..ec0d48282a 100644 --- a/Libs/DICOM/Core/ctkDICOMEchoJob.cpp +++ b/Libs/DICOM/Core/ctkDICOMEchoJob.cpp @@ -30,7 +30,7 @@ #include "ctkDICOMEchoWorker.h" #include "ctkDICOMServer.h" -static ctkLogger logger ( "org.commontk.dicom.DICOMRetrieveJob" ); +static ctkLogger logger ( "org.commontk.dicom.DICOMEchoJob" ); //------------------------------------------------------------------------------ // ctkDICOMEchoJobPrivate methods @@ -85,10 +85,17 @@ void ctkDICOMEchoJob::setServer(const ctkDICOMServer& server) } //---------------------------------------------------------------------------- -QString ctkDICOMEchoJob::loggerReport(const QString& status) const +QString ctkDICOMEchoJob::loggerReport(const QString& status) { - return QString("ctkDICOMEchoJob: echo job %1.\n") - .arg(status); + QString fullLogMsg = QString("ctkDICOMEchoJob: echo job %1.\n") + .arg(status); + QString logMsg = QString("Echo job %1.\n") + .arg(status); + QString currentDateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss.zzz"); + QString logHeader = currentDateTime + " INFO: "; + this->LoggedText += logHeader; + this->LoggedText += logMsg; + return fullLogMsg; } //------------------------------------------------------------------------------ ctkAbstractJob* ctkDICOMEchoJob::clone() const diff --git a/Libs/DICOM/Core/ctkDICOMEchoJob.h b/Libs/DICOM/Core/ctkDICOMEchoJob.h index f93bdc0715..98d42d0c31 100644 --- a/Libs/DICOM/Core/ctkDICOMEchoJob.h +++ b/Libs/DICOM/Core/ctkDICOMEchoJob.h @@ -54,7 +54,7 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMEchoJob : public ctkDICOMJob ///@} /// Logger report string formatting for specific task - Q_INVOKABLE QString loggerReport(const QString& status) const override; + Q_INVOKABLE QString loggerReport(const QString& status) override; /// \see ctkAbstractJob::clone() Q_INVOKABLE ctkAbstractJob* clone() const override; diff --git a/Libs/DICOM/Core/ctkDICOMEchoWorker.cpp b/Libs/DICOM/Core/ctkDICOMEchoWorker.cpp index 2435a047b2..310ca798fa 100644 --- a/Libs/DICOM/Core/ctkDICOMEchoWorker.cpp +++ b/Libs/DICOM/Core/ctkDICOMEchoWorker.cpp @@ -25,12 +25,14 @@ #include // ctkDICOMCore includes -#include "ctkDICOMJobResponseSet.h" #include "ctkDICOMEchoWorker_p.h" #include "ctkDICOMEchoJob.h" #include "ctkDICOMScheduler.h" #include "ctkDICOMServer.h" +// DCMTK includes +#include + static ctkLogger logger ("org.commontk.dicom.DICOMRetrieveWorker"); //------------------------------------------------------------------------------ @@ -109,6 +111,9 @@ void ctkDICOMEchoWorker::run() return; } + QString currentThread = dcmtk::log4cplus::thread::getCurrentThreadName().c_str(); + echoJob->setRunningThreadID(currentThread); + QSharedPointer scheduler = qSharedPointerObjectCast(this->Scheduler); ctkDICOMServer* server = echoJob->server(); diff --git a/Libs/DICOM/Core/ctkDICOMIndexer_p.h b/Libs/DICOM/Core/ctkDICOMIndexer_p.h index 3357a046e6..7ffdb5050b 100644 --- a/Libs/DICOM/Core/ctkDICOMIndexer_p.h +++ b/Libs/DICOM/Core/ctkDICOMIndexer_p.h @@ -259,7 +259,6 @@ class ctkDICOMIndexerPrivate : public QObject Q_SIGNALS: void startWorker(); - public: DICOMIndexingQueue RequestQueue; QThread WorkerThread; diff --git a/Libs/DICOM/Core/ctkDICOMInserter.cpp b/Libs/DICOM/Core/ctkDICOMInserter.cpp index 15f8764358..fcb337e226 100644 --- a/Libs/DICOM/Core/ctkDICOMInserter.cpp +++ b/Libs/DICOM/Core/ctkDICOMInserter.cpp @@ -24,14 +24,14 @@ // Qt includes #include +// ctkCore includes +#include + // ctkDICOMCore includes -#include "ctkLogger.h" #include "ctkDICOMDatabase.h" #include "ctkDICOMInserter.h" #include "ctkDICOMJobResponseSet.h" -static ctkLogger logger ("org.commontk.dicom.DICOMInserter"); - //------------------------------------------------------------------------------ class ctkDICOMInserterPrivate { @@ -83,7 +83,7 @@ bool ctkDICOMInserter::wasCanceled() } //------------------------------------------------------------------------------ -bool ctkDICOMInserter::addJobResponseSets(QList> jobResponseSets) +bool ctkDICOMInserter::addJobResponseSets(const QList& jobResponseSets) { Q_D(const ctkDICOMInserter); if (d->Canceled) diff --git a/Libs/DICOM/Core/ctkDICOMInserter.h b/Libs/DICOM/Core/ctkDICOMInserter.h index 96e9fdb085..d10dbc848e 100644 --- a/Libs/DICOM/Core/ctkDICOMInserter.h +++ b/Libs/DICOM/Core/ctkDICOMInserter.h @@ -67,7 +67,7 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMInserter : public QObject Q_INVOKABLE bool wasCanceled(); /// add JobResponseSets from queries and retrieves - Q_INVOKABLE bool addJobResponseSets(QList> jobResponseSets); + Q_INVOKABLE bool addJobResponseSets(const QList& jobResponseSets); Q_SIGNALS: void updatingDatabase(bool); diff --git a/Libs/DICOM/Core/ctkDICOMInserterJob.cpp b/Libs/DICOM/Core/ctkDICOMInserterJob.cpp index 5cd69c8b9e..8bfdac7919 100644 --- a/Libs/DICOM/Core/ctkDICOMInserterJob.cpp +++ b/Libs/DICOM/Core/ctkDICOMInserterJob.cpp @@ -42,12 +42,33 @@ ctkDICOMInserterJob::ctkDICOMInserterJob() ctkDICOMInserterJob::~ctkDICOMInserterJob() = default; //------------------------------------------------------------------------------ -QString ctkDICOMInserterJob::loggerReport(const QString& status) const +QString ctkDICOMInserterJob::loggerReport(const QString& status) { - return QString("ctkDICOMInserterJob: insert job %1.\n" - "Number of jobResponseSet to process: %2\n") - .arg(status) - .arg(this->JobResponseSets.count()); + QString fullLogMsg; + QString logMsg; + if (status == "started") + { + fullLogMsg = QString("ctkDICOMInserterJob: insert job %1. " + "Number of jobResponseSet to process: %2\n") + .arg(status) + .arg(this->JobResponseSets.count()); + logMsg = QString("Insert job %1. " + "Number of jobResponseSet to process: %2\n") + .arg(status) + .arg(this->JobResponseSets.count()); + } + else + { + fullLogMsg = QString("ctkDICOMInserterJob: insert job %1.\n") + .arg(status); + logMsg = QString("insert job %1. ") + .arg(status); + } + QString currentDateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss.zzz"); + QString logHeader = currentDateTime + " INFO: "; + this->LoggedText += logHeader; + this->LoggedText += logMsg; + return fullLogMsg; } //------------------------------------------------------------------------------ diff --git a/Libs/DICOM/Core/ctkDICOMInserterJob.h b/Libs/DICOM/Core/ctkDICOMInserterJob.h index 31e00457ae..ecc6a3b34c 100644 --- a/Libs/DICOM/Core/ctkDICOMInserterJob.h +++ b/Libs/DICOM/Core/ctkDICOMInserterJob.h @@ -50,7 +50,7 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMInserterJob : public ctkDICOMJob virtual ~ctkDICOMInserterJob(); /// Logger report string formatting for specific task - Q_INVOKABLE QString loggerReport(const QString& status) const override; + Q_INVOKABLE QString loggerReport(const QString& status) override; ///@{ /// Database Filename diff --git a/Libs/DICOM/Core/ctkDICOMInserterWorker.cpp b/Libs/DICOM/Core/ctkDICOMInserterWorker.cpp index c8074d9a7d..160fa91dcc 100644 --- a/Libs/DICOM/Core/ctkDICOMInserterWorker.cpp +++ b/Libs/DICOM/Core/ctkDICOMInserterWorker.cpp @@ -118,7 +118,7 @@ void ctkDICOMInserterWorker::run() .arg(inserterJob->jobUID()) .arg(QString::number(reinterpret_cast(QThread::currentThreadId())), 16)); - QList> jobResponseSets = inserterJob->jobResponseSetsShared(); + QList jobResponseSets = inserterJob->jobResponseSets(); d->Inserter->addJobResponseSets(jobResponseSets); if (d->Inserter->wasCanceled()) @@ -127,7 +127,7 @@ void ctkDICOMInserterWorker::run() return; } - foreach (QSharedPointer jobResponseSet, jobResponseSets) + foreach (ctkDICOMJobResponseSet* jobResponseSet, jobResponseSets) { emit inserterJob->progressJobDetail(jobResponseSet->toVariant()); } diff --git a/Libs/DICOM/Core/ctkDICOMJob.cpp b/Libs/DICOM/Core/ctkDICOMJob.cpp index 00c1cf46bd..f97fe73988 100644 --- a/Libs/DICOM/Core/ctkDICOMJob.cpp +++ b/Libs/DICOM/Core/ctkDICOMJob.cpp @@ -184,3 +184,9 @@ QVariant ctkDICOMJob::toVariant() { return QVariant::fromValue(ctkDICOMJobDetail(*this)); } + +//------------------------------------------------------------------------------ +void ctkDICOMJob::freeUsedResources() +{ + this->JobResponseSets.clear(); +} diff --git a/Libs/DICOM/Core/ctkDICOMJob.h b/Libs/DICOM/Core/ctkDICOMJob.h index 47aa796aa9..9d2014380c 100644 --- a/Libs/DICOM/Core/ctkDICOMJob.h +++ b/Libs/DICOM/Core/ctkDICOMJob.h @@ -117,9 +117,12 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMJob : public ctkAbstractJob /// \sa ctkDICOMJobDetail Q_INVOKABLE virtual QVariant toVariant() override; + /// Free used resources from job + /// \sa ctkAbstractJob::freeUsedResources + Q_INVOKABLE virtual void freeUsedResources() override; + Q_SIGNALS: void progressJobDetail(QVariant); - void finishedJobDetail(QVariant); protected: QString PatientID; @@ -166,6 +169,22 @@ struct CTK_DICOM_CORE_EXPORT ctkDICOMJobDetail : ctkJobDetail this->SOPInstanceUID = responseSet.sopInstanceUID(); this->ConnectionName = responseSet.connectionName(); this->NumberOfDataSets = responseSet.datasets().count(); + if (this->JobType == ctkDICOMJobResponseSet::JobType::QueryPatients) + { + this->QueriedPatientIDs = responseSet.datasets().keys(); + } + else if (this->JobType == ctkDICOMJobResponseSet::JobType::QueryStudies) + { + this->QueriedStudyInstanceUIDs = responseSet.datasets().keys(); + } + else if (this->JobType == ctkDICOMJobResponseSet::JobType::QuerySeries) + { + this->QueriedSeriesInstanceUIDs = responseSet.datasets().keys(); + } + else if (this->JobType == ctkDICOMJobResponseSet::JobType::QueryInstances) + { + this->QueriedSOPInstanceUIDs = responseSet.datasets().keys(); + } } virtual ~ctkDICOMJobDetail() = default; @@ -174,6 +193,10 @@ struct CTK_DICOM_CORE_EXPORT ctkDICOMJobDetail : ctkJobDetail QString SeriesInstanceUID; QString SOPInstanceUID; QString ReferenceInserterJobUID; + QStringList QueriedPatientIDs; + QStringList QueriedStudyInstanceUIDs; + QStringList QueriedSeriesInstanceUIDs; + QStringList QueriedSOPInstanceUIDs; // Common to DICOM Query and Retrieve jobs, and DICOM JobResponseSet QString ConnectionName; diff --git a/Libs/DICOM/Core/ctkDICOMJobResponseSet.h b/Libs/DICOM/Core/ctkDICOMJobResponseSet.h index 17aae531fd..1aba0605be 100644 --- a/Libs/DICOM/Core/ctkDICOMJobResponseSet.h +++ b/Libs/DICOM/Core/ctkDICOMJobResponseSet.h @@ -89,7 +89,8 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMJobResponseSet : public QObject RetrieveSOPInstance, StoreSOPInstance, Inserter, - Echo + Echo, + ThumbnailGenerator, }; void setJobType(JobType jobType); JobType jobType() const; diff --git a/Libs/DICOM/Core/ctkDICOMQuery.cpp b/Libs/DICOM/Core/ctkDICOMQuery.cpp index d1ca7f2901..3ee5bb2f64 100644 --- a/Libs/DICOM/Core/ctkDICOMQuery.cpp +++ b/Libs/DICOM/Core/ctkDICOMQuery.cpp @@ -32,9 +32,11 @@ #include #include +// ctkCore includes +#include + // ctkDICOMCore includes #include "ctkDICOMQuery.h" -#include "ctkLogger.h" #include "ctkDICOMJobResponseSet.h" // DCMTK includes @@ -49,7 +51,7 @@ #include /* for class OFStandard */ #include /* for class DicomDirInterface */ -static ctkLogger logger ( "org.commontk.dicom.DICOMQuery" ); +dcmtk::log4cplus::Logger rootLogQuery = dcmtk::log4cplus::Logger::getRoot(); //------------------------------------------------------------------------------ // A customized implementation so that Qt signals can be emitted @@ -80,7 +82,8 @@ class ctkDICOMQuerySCUPrivate : public DcmSCU return EC_IllegalCall; } - logger.debug ( "FIND RESPONSE" ); + QString debug = ctkDICOMQuery::tr("FIND RESPONSE"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); emit this->query->debug(/*no tr*/"Got a find response!"); return this->DcmSCU::handleFINDResponse(presID, response, waitForNextResponse); }; @@ -297,13 +300,15 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database) // message is updated but only if the progress value is (e.g. QProgressDialog) if (database.database().isOpen()) { - logger.debug("DB open in Query"); - emit progress(tr("DB open in Query")); + QString debug = ctkDICOMQuery::tr("DB open in Query"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); } else { - logger.warn("DB not open in Query"); - emit progress(tr("DB not open in Query")); + QString warn = ctkDICOMQuery::tr("DB not open in Query"); + DCMTK_LOG4CPLUS_WARN_STR(rootLogQuery, warn.toStdString().c_str()); + emit progress(warn); } emit progress(0); if (d->Canceled) @@ -354,13 +359,15 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database) presentationContext = d->SCU->findPresentationContextID(UID_FINDStudyRootQueryRetrieveInformationModel, ""); if (presentationContext == 0) { - logger.error("Failed to find acceptable presentation context"); - emit progress(tr("Failed to find acceptable presentation context")); + QString error = ctkDICOMQuery::tr("Failed to find acceptable presentation context"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogQuery, error.toStdString().c_str()); + emit progress(error); } else { - logger.debug("Found useful presentation context"); - emit progress(tr("Found useful presentation context")); + QString debug = ctkDICOMQuery::tr("Found useful presentation context"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); } emit progress(40); if (d->Canceled) @@ -371,14 +378,17 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database) OFCondition status = d->SCU->sendFINDRequest(presentationContext, d->QueryDcmDataset.data(), &responses); if (!status.good()) { - logger.error("Find failed"); - emit progress(tr("Find failed")); + QString error = ctkDICOMQuery::tr("Find failed"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogQuery, error.toStdString().c_str()); + emit progress(error); d->releaseAssociation(); emit progress(100); return false; } - logger.debug("Find succeeded"); - emit progress(tr("Find succeeded")); + + QString debug = ctkDICOMQuery::tr("Find succeeded"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); emit progress(50); if (d->Canceled) { @@ -430,8 +440,9 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database) studyDataset->findAndGetElement(DCM_PatientName, patientName); studyDataset->findAndGetElement(DCM_PatientID, patientID); - logger.debug("Starting Series C-FIND for Study: " + studyInstanceUID); - emit progress(tr("Starting Series C-FIND for Study: ") + studyInstanceUID); + QString debug = ctkDICOMQuery::tr("Starting Series C-FIND for Study: ") + studyInstanceUID; + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); emit progress(50 + (progressRatio * i++)); if (d->Canceled) { @@ -458,8 +469,10 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database) database.insert(dataset, false /* do not store */, false /* no thumbnail */); } } - logger.debug ("Find succeeded on Series level for Study: " + studyInstanceUID); - emit progress(tr("Find succeeded on Series level for Study: ") + studyInstanceUID); + + QString debug = ctkDICOMQuery::tr("Find succeeded on Series level for Study: ") + studyInstanceUID; + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); emit progress(50 + (progressRatio * i++)); if (d->Canceled) { @@ -468,8 +481,9 @@ bool ctkDICOMQuery::query(ctkDICOMDatabase& database) } else { - logger.error ("Find on Series level failed for Study: " + studyInstanceUID); - emit progress(tr("Find on Series level failed for Study: ") + studyInstanceUID); + QString error = ctkDICOMQuery::tr("Find on Series level failed for Study: ") + studyInstanceUID; + DCMTK_LOG4CPLUS_ERROR_STR(rootLogQuery, error.toStdString().c_str()); + emit progress(error); } emit progress(50 + (progressRatio * i++)); if (d->Canceled) @@ -530,13 +544,15 @@ bool ctkDICOMQuery::queryPatients() presentationContext = d->SCU->findPresentationContextID(UID_FINDStudyRootQueryRetrieveInformationModel, ""); if (presentationContext == 0) { - logger.error("Failed to find acceptable presentation context"); - emit progress(tr("Failed to find acceptable presentation context")); + QString error = ctkDICOMQuery::tr("Failed to find acceptable presentation context"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogQuery, error.toStdString().c_str()); + emit progress(error); } else { - logger.debug("Found useful presentation context"); - emit progress(tr("Found useful presentation context")); + QString debug = ctkDICOMQuery::tr("Found useful presentation context"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); } emit progress(40); if (d->Canceled) @@ -544,8 +560,9 @@ bool ctkDICOMQuery::queryPatients() return false; } - logger.debug("Starting Patients C-FIND"); - emit progress(tr("Starting Patients C-FIND")); + QString debug = ctkDICOMQuery::tr("Starting Patients C-FIND"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); emit progress(50); if (d->Canceled) { @@ -569,8 +586,10 @@ bool ctkDICOMQuery::queryPatients() contResponses++; if (contResponses > d->MaximumPatientsQuery) { - logger.warn(QString("ctkDICOMQuery: the number of responses of the query task at patients level " - "surpassed the maximum value of permitted results (i.e. %1).").arg(d->MaximumPatientsQuery)); + QString warn = ctkDICOMQuery::tr("The number of responses of the query task at patients level " + "surpassed the maximum value of permitted results (i.e. %1).").arg(d->MaximumPatientsQuery); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, warn.toStdString().c_str()); + emit progress(warn); break; } DcmDataset *dataset = (*it)->m_dataset; @@ -582,16 +601,25 @@ bool ctkDICOMQuery::queryPatients() } } - JobResponseSet->setDatasets(datasetsMap); - d->JobResponseSets.append(JobResponseSet); + if (contResponses == 0) + { + QString warn = ctkDICOMQuery::tr("The patients query provided no results. Please refine your filters."); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, warn.toStdString().c_str()); + emit progress(warn); + } - logger.debug("Find succeeded on Patient level"); - emit progress(tr("Find succeeded on Patient level")); + JobResponseSet->setDatasets(datasetsMap); + d->JobResponseSets.append(JobResponseSet); + + QString debug = ctkDICOMQuery::tr("Find succeeded on Patient level"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); } else { - logger.error("Find on Patient level failed"); - emit progress(tr("Find on Patient level failed")); + QString error = ctkDICOMQuery::tr("Find on Patient level failed"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogQuery, error.toStdString().c_str()); + emit progress(error); } emit progress(100); @@ -664,13 +692,15 @@ bool ctkDICOMQuery::queryStudies(const QString& patientID) presentationContext = d->SCU->findPresentationContextID(UID_FINDStudyRootQueryRetrieveInformationModel, ""); if (presentationContext == 0) { - logger.error("Failed to find acceptable presentation context"); - emit progress(tr("Failed to find acceptable presentation context")); + QString error = ctkDICOMQuery::tr("Failed to find acceptable presentation context"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogQuery, error.toStdString().c_str()); + emit progress(error); } else { - logger.debug("Found useful presentation context"); - emit progress(tr("Found useful presentation context")); + QString debug = ctkDICOMQuery::tr("Found useful presentation context"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); } emit progress(40); if (d->Canceled) @@ -678,8 +708,9 @@ bool ctkDICOMQuery::queryStudies(const QString& patientID) return false; } - logger.debug("Starting Studies C-FIND for Patient: " + patientID); - emit progress(tr("Starting Studies C-FIND for Patient: ") + patientID); + QString debug = ctkDICOMQuery::tr("Starting Studies C-FIND for Patient: ") + patientID; + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); emit progress(50); if (d->Canceled) { @@ -715,13 +746,15 @@ bool ctkDICOMQuery::queryStudies(const QString& patientID) JobResponseSet->setDatasets(datasetsMap); d->JobResponseSets.append(JobResponseSet); - logger.debug("Find succeeded on Study level for Patient: " + patientID); - emit progress(tr("Find succeeded on Study level for Patient: ") + patientID); + QString debug = ctkDICOMQuery::tr("Find succeeded on Study level for Patient: ") + patientID; + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); } else { - logger.error("Find on Study level failed for Patient: " + patientID); - emit progress(tr("Find on Study level failed for Patient: ") + patientID); + QString error = ctkDICOMQuery::tr("Find on Study level failed for Patient: ") + patientID; + DCMTK_LOG4CPLUS_ERROR_STR(rootLogQuery, error.toStdString().c_str()); + emit progress(error); } emit progress(100); @@ -790,13 +823,15 @@ bool ctkDICOMQuery::querySeries(const QString& patientID, presentationContext = d->SCU->findPresentationContextID(UID_FINDStudyRootQueryRetrieveInformationModel, ""); if (presentationContext == 0) { - logger.error("Failed to find acceptable presentation context"); - emit progress(tr("Failed to find acceptable presentation context")); + QString error = ctkDICOMQuery::tr("Failed to find acceptable presentation context"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogQuery, error.toStdString().c_str()); + emit progress(error); } else { - logger.debug("Found useful presentation context"); - emit progress(tr("Found useful presentation context")); + QString debug = ctkDICOMQuery::tr("Found useful presentation context"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); } emit progress(40); if (d->Canceled) @@ -804,8 +839,9 @@ bool ctkDICOMQuery::querySeries(const QString& patientID, return false; } - logger.debug("Starting Series C-FIND for Study: " + studyInstanceUID); - emit progress(tr("Starting Series C-FIND for Study: ") + studyInstanceUID); + QString debug = ctkDICOMQuery::tr("Starting Series C-FIND for Study: ") + studyInstanceUID; + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); emit progress(50); if (d->Canceled) { @@ -843,13 +879,15 @@ bool ctkDICOMQuery::querySeries(const QString& patientID, JobResponseSet->setDatasets(datasetsMap); d->JobResponseSets.append(JobResponseSet); - logger.debug("Find succeeded on Series level for Study: " + studyInstanceUID); - emit progress(tr("Find succeeded on Series level for Study: ") + studyInstanceUID); + QString debug = ctkDICOMQuery::tr("Find succeeded on Series level for Study: ") + studyInstanceUID; + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); } else { - logger.error("Find on Series level failed for Study: " + studyInstanceUID); - emit progress(tr("Find on Series level failed for Study: ") + studyInstanceUID); + QString error = ctkDICOMQuery::tr("Find on Series level failed for Study: ") + studyInstanceUID; + DCMTK_LOG4CPLUS_ERROR_STR(rootLogQuery, error.toStdString().c_str()); + emit progress(error); } emit progress(100); @@ -915,13 +953,15 @@ bool ctkDICOMQuery::queryInstances(const QString& patientID, d->PresentationContext = d->SCU->findPresentationContextID(UID_FINDStudyRootQueryRetrieveInformationModel, ""); if (d->PresentationContext == 0) { - logger.error("Failed to find acceptable presentation context"); - emit progress(tr("Failed to find acceptable presentation context")); + QString error = ctkDICOMQuery::tr("Failed to find acceptable presentation context"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogQuery, error.toStdString().c_str()); + emit progress(error); } else { - logger.debug("Found useful presentation context"); - emit progress(tr("Found useful presentation context")); + QString debug = ctkDICOMQuery::tr("Found useful presentation context"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); } emit progress(40); if (d->Canceled) @@ -929,8 +969,9 @@ bool ctkDICOMQuery::queryInstances(const QString& patientID, return false; } - logger.debug("Starting Instances C-FIND for Series: " + seriesInstanceUID); - emit progress(tr("Starting Instances C-FIND for Series: ") + seriesInstanceUID); + QString debug = ctkDICOMQuery::tr("Starting Instances C-FIND for Series: ") + seriesInstanceUID; + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); emit progress(50); if (d->Canceled) { @@ -966,13 +1007,16 @@ bool ctkDICOMQuery::queryInstances(const QString& patientID, datasetsMap.insert(SOPInstanceUID.c_str(), dataset); } } - logger.debug("Find succeeded on Series level for Series: " + seriesInstanceUID); - emit progress(tr("Find succeeded on Series level for Series: ") + seriesInstanceUID); + + QString debug = ctkDICOMQuery::tr("Find succeeded on Series level for Series: ") + seriesInstanceUID; + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); } else { - logger.error("Find on Series level failed for Series: " + seriesInstanceUID); - emit progress(tr("Find on Series level failed for Series: ") + seriesInstanceUID); + QString error = ctkDICOMQuery::tr("Find on Series level failed for Series: ") + seriesInstanceUID; + DCMTK_LOG4CPLUS_ERROR_STR(rootLogQuery, error.toStdString().c_str()); + emit progress(error); } JobResponseSet->setDatasets(datasetsMap); @@ -1018,8 +1062,9 @@ bool ctkDICOMQuery::initializeSCU() d->SCU->setPeerHostName(OFString(this->host().toStdString().c_str())); d->SCU->setPeerPort(this->port()); - logger.debug("Setting Transfer Syntaxes"); - emit progress(tr("Setting Transfer Syntaxes")); + QString debug = ctkDICOMQuery::tr("Setting Transfer Syntaxes"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); emit progress(10); if (d->Canceled) { @@ -1034,13 +1079,16 @@ bool ctkDICOMQuery::initializeSCU() d->SCU->addPresentationContext(UID_FINDStudyRootQueryRetrieveInformationModel, transferSyntaxes); if (!d->SCU->initNetwork().good()) { - logger.error("Error initializing the network"); - emit progress(tr("Error initializing the network")); + QString error = ctkDICOMQuery::tr("Error initializing the network"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogQuery, error.toStdString().c_str()); + emit progress(error); emit progress(100); return false; } - logger.debug("Negotiating Association"); - emit progress(tr("Negotiating Association")); + + debug = ctkDICOMQuery::tr("Negotiating Association"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); emit progress(20); if (d->Canceled) { @@ -1050,8 +1098,9 @@ bool ctkDICOMQuery::initializeSCU() OFCondition result = d->SCU->negotiateAssociation(); if (result.bad()) { - logger.error("Error negotiating the association: " + QString(result.text())); - emit progress(tr("Error negotiating the association")); + QString error = ctkDICOMQuery::tr("Error negotiating the association: ") + QString(result.text()); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogQuery, error.toStdString().c_str()); + emit progress(error); emit progress(100); return false; } @@ -1104,7 +1153,11 @@ QString ctkDICOMQuery::applyFilters(QMap filters) modalitySearch += modality + QString("\\"); } modalitySearch.chop(1); // remove final backslash - logger.debug("modalityInStudySearch " + modalitySearch); + + QString debug = ctkDICOMQuery::tr("modalityInStudySearch ") + modalitySearch; + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); + d->QueryDcmDataset->putAndInsertString(DCM_ModalitiesInStudy, modalitySearch.toLatin1().data()); } // Remember Series Description for later series query if we go through the keys now @@ -1115,7 +1168,9 @@ QString ctkDICOMQuery::applyFilters(QMap filters) } else { - logger.debug("Ignoring unknown search key: " + key); + QString debug = ctkDICOMQuery::tr("Ignoring unknown search key: ") + key; + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); } } @@ -1126,7 +1181,10 @@ QString ctkDICOMQuery::applyFilters(QMap filters) QString("-") + filters["EndDate"].toString(); d->QueryDcmDataset->putAndInsertString (DCM_StudyDate, dateRange.toLatin1().data()); - logger.debug("Query on study date " + dateRange); + + QString debug = ctkDICOMQuery::tr("Query on study date ") + dateRange; + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogQuery, debug.toStdString().c_str()); + emit progress(debug); } emit progress(30); diff --git a/Libs/DICOM/Core/ctkDICOMQueryJob.cpp b/Libs/DICOM/Core/ctkDICOMQueryJob.cpp index aa1a810a45..e6e668373c 100644 --- a/Libs/DICOM/Core/ctkDICOMQueryJob.cpp +++ b/Libs/DICOM/Core/ctkDICOMQueryJob.cpp @@ -104,53 +104,75 @@ void ctkDICOMQueryJob::setServer(const ctkDICOMServer& server) } //---------------------------------------------------------------------------- -QString ctkDICOMQueryJob::loggerReport(const QString& status) const +QString ctkDICOMQueryJob::loggerReport(const QString& status) { + QString fullLogMsg; + QString logMsg; switch (this->dicomLevel()) { case ctkDICOMJob::DICOMLevels::Patients: - return QString("ctkDICOMQueryJob: query job at patients level %1.\n" - "JobUID: %2\n" - "Server: %3\n") - .arg(status) - .arg(this->jobUID()) - .arg(this->server()->connectionName()); + fullLogMsg = QString("ctkDICOMQueryJob: query job at patients level %1.\n" + "JobUID: %2\n" + "Server: %3\n") + .arg(status) + .arg(this->jobUID()) + .arg(this->server()->connectionName()); + logMsg = QString("Query job at patients level %1.\n") + .arg(status); + break; case ctkDICOMJob::DICOMLevels::Studies: - return QString("ctkDICOMQueryJob: query job at studies level %1.\n" - "JobUID: %2\n" - "Server: %3\n" - "PatientID: %4\n") - .arg(status) - .arg(this->jobUID()) - .arg(this->server()->connectionName()) - .arg(this->patientID()); + fullLogMsg = QString("ctkDICOMQueryJob: query job at studies level %1.\n" + "JobUID: %2\n" + "Server: %3\n" + "PatientID: %4\n") + .arg(status) + .arg(this->jobUID()) + .arg(this->server()->connectionName()) + .arg(this->patientID()); + logMsg = QString("Query job at studies level %1.\n") + .arg(status); + break; case ctkDICOMJob::DICOMLevels::Series: - return QString("ctkDICOMQueryJob: query job at series level %1.\n" - "JobUID: %2\n" - "Server: %3\n" - "PatientID: %4\n" - "StudyInstanceUID: %5\n") - .arg(status) - .arg(this->jobUID()) - .arg(this->server()->connectionName()) - .arg(this->patientID()) - .arg(this->studyInstanceUID()); + fullLogMsg = QString("ctkDICOMQueryJob: query job at series level %1.\n" + "JobUID: %2\n" + "Server: %3\n" + "PatientID: %4\n" + "StudyInstanceUID: %5\n") + .arg(status) + .arg(this->jobUID()) + .arg(this->server()->connectionName()) + .arg(this->patientID()) + .arg(this->studyInstanceUID()); + logMsg = QString("Query job at studies level %1.\n") + .arg(status); + break; case ctkDICOMJob::DICOMLevels::Instances: - return QString("ctkDICOMQueryJob: query job at instances level %1.\n" - "JobUID: %2\n" - "Server: %3\n" - "PatientID: %4\n" - "StudyInstanceUID: %5\n" - "SeriesInstanceUID: %6\n") - .arg(status) - .arg(this->jobUID()) - .arg(this->server()->connectionName()) - .arg(this->patientID()) - .arg(this->studyInstanceUID()) - .arg(this->seriesInstanceUID()); + fullLogMsg = QString("ctkDICOMQueryJob: query job at instances level %1.\n" + "JobUID: %2\n" + "Server: %3\n" + "PatientID: %4\n" + "StudyInstanceUID: %5\n" + "SeriesInstanceUID: %6\n") + .arg(status) + .arg(this->jobUID()) + .arg(this->server()->connectionName()) + .arg(this->patientID()) + .arg(this->studyInstanceUID()) + .arg(this->seriesInstanceUID()); + logMsg = QString("Query job at instances level %1.\n") + .arg(status); + break; default: - return QString(""); + fullLogMsg = QString(""); + logMsg = QString(""); + break; } + + QString currentDateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss.zzz"); + QString logHeader = currentDateTime + " INFO: "; + this->LoggedText += logHeader; + this->LoggedText += logMsg; + return fullLogMsg; } //------------------------------------------------------------------------------ diff --git a/Libs/DICOM/Core/ctkDICOMQueryJob.h b/Libs/DICOM/Core/ctkDICOMQueryJob.h index de42916455..ed5cffe8b9 100644 --- a/Libs/DICOM/Core/ctkDICOMQueryJob.h +++ b/Libs/DICOM/Core/ctkDICOMQueryJob.h @@ -85,7 +85,7 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMQueryJob : public ctkDICOMJob ///@} /// Logger report string formatting for specific task - Q_INVOKABLE QString loggerReport(const QString& status) const override; + Q_INVOKABLE QString loggerReport(const QString& status) override; /// \see ctkAbstractJob::clone() Q_INVOKABLE ctkAbstractJob* clone() const override; diff --git a/Libs/DICOM/Core/ctkDICOMRetrieve.cpp b/Libs/DICOM/Core/ctkDICOMRetrieve.cpp index 790d107ca7..5f91e1edf5 100644 --- a/Libs/DICOM/Core/ctkDICOMRetrieve.cpp +++ b/Libs/DICOM/Core/ctkDICOMRetrieve.cpp @@ -23,10 +23,11 @@ // Qt includes #include +// ctkCore includes +#include + // ctkDICOMCore includes #include "ctkDICOMRetrieve.h" -#include "ctkErrorLogLevel.h" -#include "ctkLogger.h" #include "ctkDICOMJobResponseSet.h" // DCMTK includes @@ -46,7 +47,7 @@ #include /* for DcmRLEDecoderRegistration */ #include /* for DcmRLEEncoderRegistration */ -static ctkLogger logger("org.commontk.dicom.DICOMRetrieve"); +dcmtk::log4cplus::Logger rootLogRetrieve = dcmtk::log4cplus::Logger::getRoot(); //------------------------------------------------------------------------------ // A customized local implementation of the DcmSCU so that Qt signals can be emitted @@ -274,7 +275,6 @@ ctkDICOMRetrievePrivate::ctkDICOMRetrievePrivate(ctkDICOMRetrieve& obj) // register RLE decompression codec DcmRLEDecoderRegistration::registerCodecs(); - logger.debug("Setting Transfer Syntaxes"); OFList transferSyntaxes; transferSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax); transferSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax); @@ -355,22 +355,26 @@ bool ctkDICOMRetrievePrivate::initializeSCU(const QString& patientID, // Check and initialize networking parameters in DCMTK if ( !this->SCU->initNetwork().good() ) { - logger.error ( "Error initializing the network" ); + QString error = ctkDICOMRetrieve::tr("Error initializing the network"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogRetrieve, error.toStdString().c_str()); return false; } // Negotiate (i.e. start the) association - logger.debug ( "Negotiating Association" ); + QString debug = ctkDICOMRetrieve::tr("Negotiating Association"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogRetrieve, debug.toStdString().c_str()); if ( !this->SCU->negotiateAssociation().good() ) { - logger.error ( "Error negotiating association" ); + QString error = ctkDICOMRetrieve::tr("Error negotiating association"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogRetrieve, error.toStdString().c_str()); return false;; } } this->ConnectionParamsChanged = false; // Setup query about what to be received from the PACS - logger.debug ( "Setting Retrieve Parameters" ); + QString debug = ctkDICOMRetrieve::tr("Setting Retrieve Parameters"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogRetrieve, debug.toStdString().c_str()); if (retrieveType == ctkDICOMRetrieve::RetrieveSOPInstance) { retrieveParameters->putAndInsertString(DCM_QueryRetrieveLevel, "IMAGE"); @@ -446,19 +450,22 @@ bool ctkDICOMRetrievePrivate::move(const QString& patientID, retrieveParameters)) { delete retrieveParameters; - logger.error("MOVE Request failed: SCU initialization failed"); + QString error = ctkDICOMRetrieve::tr("MOVE Request failed: SCU initialization failed"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogRetrieve, error.toStdString().c_str()); return false; } // Issue request - logger.debug ( "Sending Move Request" ); + QString debug = ctkDICOMRetrieve::tr("Sending Move Request"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogRetrieve, debug.toStdString().c_str()); OFList responses; this->PresentationContext = this->SCU->findPresentationContextID( UID_MOVEStudyRootQueryRetrieveInformationModel, "" /* don't care about transfer syntax */); if (this->PresentationContext == 0) { - logger.error ( "MOVE Request failed: No valid Study Root MOVE Presentation Context available" ); + QString error = ctkDICOMRetrieve::tr("MOVE Request failed: No valid Study Root MOVE Presentation Context available"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogRetrieve, error.toStdString().c_str()); if (!this->KeepAssociationOpen) { this->releaseAssociation(); @@ -488,8 +495,8 @@ bool ctkDICOMRetrievePrivate::move(const QString& patientID, // If we do not receive a single response, something is fishy if ( responses.begin() == responses.end() ) { - logger.error ( "No responses received at all! (at least one empty response always expected)" ); - //throw std::runtime_error( std::string("No responses received from server!") ); + QString error = ctkDICOMRetrieve::tr("No responses received at all! (at least one empty response always expected)"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogRetrieve, error.toStdString().c_str()); return false; } @@ -509,30 +516,35 @@ bool ctkDICOMRetrievePrivate::move(const QString& patientID, if ( responses.size() == 1 ) { RetrieveResponse* rsp = *responses.begin(); - logger.debug ( "MOVE response receveid with status: " + - QString(DU_cmoveStatusString(rsp->m_status)) ); + QString debug = ctkDICOMRetrieve::tr("MOVE response receveid with status: ")+ + QString(DU_cmoveStatusString(rsp->m_status)); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogRetrieve, debug.toStdString().c_str()); if ( (rsp->m_status == STATUS_Success) || (rsp->m_status == STATUS_MOVE_Warning_SubOperationsCompleteOneOrMoreFailures)) { if (rsp->m_numberOfCompletedSubops == 0) { - logger.error ( "No images transferred by PACS!" ); + QString error = ctkDICOMRetrieve::tr("No images transferred by PACS!"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogRetrieve, error.toStdString().c_str()); + return false; } } else { - logger.debug("MOVE request failed, server does report error"); - QString statusDetail("No details"); + QString debug = ctkDICOMRetrieve::tr("MOVE request failed, server does report error"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogRetrieve, debug.toStdString().c_str()); + + QString statusDetail(ctkDICOMRetrieve::tr("No details")); if (rsp->m_statusDetail != NULL) { std::ostringstream out; rsp->m_statusDetail->print(out); - statusDetail = "Status Detail: " + statusDetail.fromStdString(out.str()); + statusDetail = ctkDICOMRetrieve::tr("Status Detail: ") + statusDetail.fromStdString(out.str()); } - statusDetail.prepend("MOVE request failed: "); - logger.debug(statusDetail); + statusDetail.prepend(ctkDICOMRetrieve::tr("MOVE request failed: ")); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogRetrieve, statusDetail.toStdString().c_str()); return false; } } @@ -544,16 +556,16 @@ bool ctkDICOMRetrievePrivate::move(const QString& patientID, { it++; } - logger.debug ( - QString("MOVE responses report for study: %1\n" + + debug = ctkDICOMRetrieve::tr("MOVE responses report for study: %1\n" "%2 images transferred, and\n" "%3 images transferred with warning, and\n" "%4 images transfers failed") .arg(studyInstanceUID) .arg(QString::number(static_cast((*it)->m_numberOfCompletedSubops))) .arg(QString::number(static_cast((*it)->m_numberOfWarningSubops))) - .arg(QString::number(static_cast((*it)->m_numberOfFailedSubops))) - ); + .arg(QString::number(static_cast((*it)->m_numberOfFailedSubops))); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogRetrieve, debug.toStdString().c_str()); if (this->Canceled) { @@ -613,7 +625,8 @@ bool ctkDICOMRetrievePrivate::get(const QString& patientID, retrieveParameters)) { delete retrieveParameters; - logger.error("MOVE Request failed: SCU initialization failed"); + QString error = ctkDICOMRetrieve::tr("MOVE Request failed: SCU initialization failed"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogRetrieve, error.toStdString().c_str()); return false; } @@ -623,8 +636,9 @@ bool ctkDICOMRetrievePrivate::get(const QString& patientID, } // Issue request - logger.debug ( "Sending Get Request" ); - emit q->progress(ctkDICOMRetrieve::tr("Sending Get Request")); + QString debug = ctkDICOMRetrieve::tr( "Sending Get Request"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogRetrieve, debug.toStdString().c_str()); + emit q->progress(debug); emit q->progress(0); OFList responses; this->PresentationContext = this->SCU->findPresentationContextID( @@ -632,7 +646,8 @@ bool ctkDICOMRetrievePrivate::get(const QString& patientID, "" /* don't care about transfer syntax */ ); if (this->PresentationContext == 0) { - logger.error ( "GET Request failed: No valid Study Root GET Presentation Context available" ); + QString error = ctkDICOMRetrieve::tr("GET Request failed: No valid Study Root GET Presentation Context available"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogRetrieve, error.toStdString().c_str()); if (!this->KeepAssociationOpen) { this->releaseAssociation(); @@ -666,9 +681,9 @@ bool ctkDICOMRetrievePrivate::get(const QString& patientID, // If we do not receive a single response, something is fishy if ( responses.begin() == responses.end() ) { - logger.error ( "No responses received at all! (at least one empty response always expected)" ); - //throw std::runtime_error( std::string("No responses received from server!") ); - emit q->progress(ctkDICOMRetrieve::tr("No Responses from Server!")); + QString error = ctkDICOMRetrieve::tr("No responses received at all! (at least one empty response always expected)"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogRetrieve, error.toStdString().c_str()); + emit q->progress(error); return false; } @@ -691,30 +706,35 @@ bool ctkDICOMRetrievePrivate::get(const QString& patientID, if ( responses.size() == 1 ) { RetrieveResponse* rsp = *responses.begin(); - logger.debug ( "GET response receveid with status: " + - QString(DU_cmoveStatusString(rsp->m_status)) ); + QString debug = ctkDICOMRetrieve::tr("GET response receveid with status: ") + + QString(DU_cmoveStatusString(rsp->m_status)); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogRetrieve, debug.toStdString().c_str()); if ( (rsp->m_status == STATUS_Success) || (rsp->m_status == STATUS_GET_Warning_SubOperationsCompleteOneOrMoreFailures)) { if (rsp->m_numberOfCompletedSubops == 0) { - logger.error ( "No images transferred by PACS!" ); + QString error = ctkDICOMRetrieve::tr("No images transferred by PACS!"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogRetrieve, error.toStdString().c_str()); return false; } } else { - logger.debug("GET request failed, server does report error"); - QString statusDetail("No details"); + QString debug = ctkDICOMRetrieve::tr("GET request failed, server does report error"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogRetrieve, debug.toStdString().c_str()); + + QString statusDetail(ctkDICOMRetrieve::tr("No details")); if (rsp->m_statusDetail != NULL) { std::ostringstream out; rsp->m_statusDetail->print(out); - statusDetail = "Status Detail: " + statusDetail.fromStdString(out.str()); + statusDetail = ctkDICOMRetrieve::tr("Status Detail: ") + statusDetail.fromStdString(out.str()); } - statusDetail.prepend("GET request failed: "); - logger.debug(statusDetail); + + statusDetail.prepend(ctkDICOMRetrieve::tr("GET request failed: ")); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogRetrieve, statusDetail.toStdString().c_str()); return false; } } @@ -726,16 +746,15 @@ bool ctkDICOMRetrievePrivate::get(const QString& patientID, it++; } - logger.debug ( - QString("GET responses report for study: %1\n" + debug = ctkDICOMRetrieve::tr("GET responses report for study: %1\n" "%2 images transferred, and\n" "%3 images transferred with warning, and\n" "%4 images transfers failed") .arg(studyInstanceUID) .arg(QString::number(static_cast((*it)->m_numberOfCompletedSubops))) .arg(QString::number(static_cast((*it)->m_numberOfWarningSubops))) - .arg(QString::number(static_cast((*it)->m_numberOfFailedSubops))) - ); + .arg(QString::number(static_cast((*it)->m_numberOfFailedSubops))); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogRetrieve, debug.toStdString().c_str()); emit q->progress(ctkDICOMRetrieve::tr("Finished Get")); emit q->progress(100); @@ -967,13 +986,17 @@ bool ctkDICOMRetrieve::wasCanceled() bool ctkDICOMRetrieve::moveStudy(const QString& studyInstanceUID, const QString& patientID) { + Q_D(ctkDICOMRetrieve); + if (studyInstanceUID.isEmpty()) { - logger.error("Cannot receive series: Study Instance UID empty."); + QString error = ctkDICOMRetrieve::tr("Cannot receive series: Study Instance UID empty."); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogRetrieve, error.toStdString().c_str()); return false; } - Q_D(ctkDICOMRetrieve); - logger.debug("Starting moveStudy"); + + QString debug = ctkDICOMRetrieve::tr("Starting moveStudy"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogRetrieve, debug.toStdString().c_str()); return d->move(patientID, studyInstanceUID, "", "", ctkDICOMRetrieve::RetrieveStudy); } @@ -981,13 +1004,17 @@ bool ctkDICOMRetrieve::moveStudy(const QString& studyInstanceUID, bool ctkDICOMRetrieve::getStudy(const QString& studyInstanceUID, const QString& patientID) { + Q_D(ctkDICOMRetrieve); + if (studyInstanceUID.isEmpty()) { - logger.error("Cannot receive study: Study Instance UID empty."); + QString error = ctkDICOMRetrieve::tr("Cannot receive study: Study Instance UID empty."); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogRetrieve, error.toStdString().c_str()); return false; } - Q_D(ctkDICOMRetrieve); - logger.debug("Starting getStudy"); + + QString debug = ctkDICOMRetrieve::tr(("Starting getStudy")); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogRetrieve, debug.toStdString().c_str()); return d->get(patientID, studyInstanceUID, "", "", ctkDICOMRetrieve::RetrieveStudy); } @@ -996,14 +1023,18 @@ bool ctkDICOMRetrieve::moveSeries(const QString& studyInstanceUID, const QString& seriesInstanceUID, const QString& patientID) { + Q_D(ctkDICOMRetrieve); + if (studyInstanceUID.isEmpty() || seriesInstanceUID.isEmpty()) { - logger.error("Cannot receive series: Study or Series Instance UID empty."); + QString error = ctkDICOMRetrieve::tr("Cannot receive series: Study or Series Instance UID empty."); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogRetrieve, error.toStdString().c_str()); return false; } - Q_D(ctkDICOMRetrieve); - logger.debug("Starting moveSeries"); + + QString debug = ctkDICOMRetrieve::tr("Starting moveSeries"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogRetrieve, debug.toStdString().c_str()); return d->move(patientID, studyInstanceUID, seriesInstanceUID, "", ctkDICOMRetrieve::RetrieveSeries); } @@ -1012,14 +1043,18 @@ bool ctkDICOMRetrieve::getSeries(const QString& studyInstanceUID, const QString& seriesInstanceUID, const QString& patientID) { + Q_D(ctkDICOMRetrieve); + if (studyInstanceUID.isEmpty() || seriesInstanceUID.isEmpty()) { - logger.error("Cannot receive series: Study or Series Instance UID empty."); + QString error = ctkDICOMRetrieve::tr("Cannot receive series: Study or Series Instance UID empty."); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogRetrieve, error.toStdString().c_str()); return false; } - Q_D(ctkDICOMRetrieve); - logger.debug("Starting getSeries"); + + QString debug = ctkDICOMRetrieve::tr("Starting getSeries"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogRetrieve, debug.toStdString().c_str()); return d->get(patientID, studyInstanceUID, seriesInstanceUID, "", ctkDICOMRetrieve::RetrieveSeries); } @@ -1035,11 +1070,13 @@ bool ctkDICOMRetrieve::moveSOPInstance(const QString& studyInstanceUID, seriesInstanceUID.isEmpty() || SOPInstanceUID.isEmpty()) { - logger.error("Cannot receive SOPInstance: Study, Series or SOP Instance UID empty."); + QString error = ctkDICOMRetrieve::tr("Cannot receive SOPInstance: Study, Series or SOP Instance UID empty."); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogRetrieve, error.toStdString().c_str()); return false; } - logger.debug("Starting moveSOPInstance"); + QString debug = ctkDICOMRetrieve::tr("Starting moveSOPInstance"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogRetrieve, debug.toStdString().c_str()); return d->move(patientID, studyInstanceUID, seriesInstanceUID, SOPInstanceUID, ctkDICOMRetrieve::RetrieveSOPInstance); } @@ -1055,12 +1092,14 @@ bool ctkDICOMRetrieve::getSOPInstance(const QString& studyInstanceUID, seriesInstanceUID.isEmpty() || SOPInstanceUID.isEmpty()) { - logger.error("Cannot receive SOPInstance: Study, Series or SOP Instance UID empty."); + QString error = ctkDICOMRetrieve::tr("Cannot receive SOPInstance: Study, Series or SOP Instance UID empty."); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogRetrieve, error.toStdString().c_str()); return false; } - logger.debug("Starting getSOPInstance"); - return d->get(patientID, studyInstanceUID, seriesInstanceUID, SOPInstanceUID, ctkDICOMRetrieve::RetrieveSOPInstance); + QString debug = ctkDICOMRetrieve::tr("Starting getSOPInstance"); + DCMTK_LOG4CPLUS_DEBUG_STR(rootLogRetrieve, debug.toStdString().c_str()); + return d->get(patientID, studyInstanceUID, seriesInstanceUID, SOPInstanceUID, ctkDICOMRetrieve::RetrieveSOPInstance); } //------------------------------------------------------------------------------ diff --git a/Libs/DICOM/Core/ctkDICOMRetrieveJob.cpp b/Libs/DICOM/Core/ctkDICOMRetrieveJob.cpp index 4dedc4dae5..eb5deaafd4 100644 --- a/Libs/DICOM/Core/ctkDICOMRetrieveJob.cpp +++ b/Libs/DICOM/Core/ctkDICOMRetrieveJob.cpp @@ -85,64 +85,87 @@ void ctkDICOMRetrieveJob::setServer(const ctkDICOMServer& server) } //---------------------------------------------------------------------------- -QString ctkDICOMRetrieveJob::loggerReport(const QString& status) const +QString ctkDICOMRetrieveJob::loggerReport(const QString& status) { + QString fullLogMsg; + QString logMsg; switch (this->dicomLevel()) { case ctkDICOMJob::DICOMLevels::None: - logger.debug(QString("ctkDICOMQueryWorker : DICOMLevels was not set\n")); - return ""; + fullLogMsg = QString("ctkDICOMRetrieveJob : DICOMLevels was not set\n"); + logMsg = QString("DICOMLevels was not set\n"); + break; case ctkDICOMJob::DICOMLevels::Patients: - return QString("ctkDICOMRetrieveJob: retrieve task at patients level %1.\n" - "JobUID: %2\n" - "Server: %3\n" - "PatientID: %4\n") - .arg(status) - .arg(this->jobUID()) - .arg(this->server()->connectionName()) - .arg(this->patientID()); + fullLogMsg = QString("ctkDICOMRetrieveJob: retrieve task at patients level %1.\n" + "JobUID: %2\n" + "Server: %3\n" + "PatientID: %4\n") + .arg(status) + .arg(this->jobUID()) + .arg(this->server()->connectionName()) + .arg(this->patientID()); + logMsg = QString("Retrieve task at patients level %1.\n") + .arg(status); + break; case ctkDICOMJob::DICOMLevels::Studies: - return QString("ctkDICOMRetrieveJob: retrieve task at studies level %1.\n" - "JobUID: %2\n" - "Server: %3\n" - "PatientID: %4\n" - "StudyInstanceUID: %5\n") - .arg(status) - .arg(this->jobUID()) - .arg(this->server()->connectionName()) - .arg(this->patientID()) - .arg(this->studyInstanceUID()); + fullLogMsg = QString("ctkDICOMRetrieveJob: retrieve task at studies level %1.\n" + "JobUID: %2\n" + "Server: %3\n" + "PatientID: %4\n" + "StudyInstanceUID: %5\n") + .arg(status) + .arg(this->jobUID()) + .arg(this->server()->connectionName()) + .arg(this->patientID()) + .arg(this->studyInstanceUID()); + logMsg = QString("Retrieve task at studies level %1.\n") + .arg(status); + break; case ctkDICOMJob::DICOMLevels::Series: - return QString("ctkDICOMRetrieveJob: retrieve task at series level %1.\n" - "JobUID: %2\n" - "Server: %3\n" - "PatientID: %4\n" - "StudyInstanceUID: %5\n" - "SeriesInstanceUID: %6\n") - .arg(status) - .arg(this->jobUID()) - .arg(this->server()->connectionName()) - .arg(this->patientID()) - .arg(this->studyInstanceUID()) - .arg(this->seriesInstanceUID()); + fullLogMsg = QString("ctkDICOMRetrieveJob: retrieve task at series level %1.\n" + "JobUID: %2\n" + "Server: %3\n" + "PatientID: %4\n" + "StudyInstanceUID: %5\n" + "SeriesInstanceUID: %6\n") + .arg(status) + .arg(this->jobUID()) + .arg(this->server()->connectionName()) + .arg(this->patientID()) + .arg(this->studyInstanceUID()) + .arg(this->seriesInstanceUID()); + logMsg = QString("Retrieve task at series level %1.\n") + .arg(status); + break; case ctkDICOMJob::DICOMLevels::Instances: - return QString("ctkDICOMRetrieveJob: retrieve task at instances level %1.\n" - "JobUID: %2\n" - "Server: %3\n" - "PatientID: %4\n" - "StudyInstanceUID: %5\n" - "SeriesInstanceUID: %6\n" - "SOPInstanceUID: %7\n") - .arg(status) - .arg(this->jobUID()) - .arg(this->server()->connectionName()) - .arg(this->patientID()) - .arg(this->studyInstanceUID()) - .arg(this->seriesInstanceUID()) - .arg(this->sopInstanceUID()); + fullLogMsg = QString("ctkDICOMRetrieveJob: retrieve task at instances level %1.\n" + "JobUID: %2\n" + "Server: %3\n" + "PatientID: %4\n" + "StudyInstanceUID: %5\n" + "SeriesInstanceUID: %6\n" + "SOPInstanceUID: %7\n") + .arg(status) + .arg(this->jobUID()) + .arg(this->server()->connectionName()) + .arg(this->patientID()) + .arg(this->studyInstanceUID()) + .arg(this->seriesInstanceUID()) + .arg(this->sopInstanceUID()); + logMsg = QString("Retrieve task at instances level %1.\n") + .arg(status); + break; default: - return QString(""); + fullLogMsg = QString(""); + logMsg = QString(""); + break; } + + QString currentDateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss.zzz"); + QString logHeader = currentDateTime + " INFO: "; + this->LoggedText += logHeader; + this->LoggedText += logMsg; + return fullLogMsg; } //------------------------------------------------------------------------------ ctkAbstractJob* ctkDICOMRetrieveJob::clone() const diff --git a/Libs/DICOM/Core/ctkDICOMRetrieveJob.h b/Libs/DICOM/Core/ctkDICOMRetrieveJob.h index 4d65ea6132..ef60958406 100644 --- a/Libs/DICOM/Core/ctkDICOMRetrieveJob.h +++ b/Libs/DICOM/Core/ctkDICOMRetrieveJob.h @@ -54,7 +54,7 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMRetrieveJob : public ctkDICOMJob ///@} /// Logger report string formatting for specific task - Q_INVOKABLE QString loggerReport(const QString& status) const override; + Q_INVOKABLE QString loggerReport(const QString& status) override; /// \see ctkAbstractJob::clone() Q_INVOKABLE ctkAbstractJob* clone() const override; diff --git a/Libs/DICOM/Core/ctkDICOMScheduler.cpp b/Libs/DICOM/Core/ctkDICOMScheduler.cpp index 95026d33a3..b3bc2fcbb9 100644 --- a/Libs/DICOM/Core/ctkDICOMScheduler.cpp +++ b/Libs/DICOM/Core/ctkDICOMScheduler.cpp @@ -28,6 +28,7 @@ // ctkDICOMCore includes #include "ctkDICOMEchoJob.h" +#include "ctkDICOMThumbnailGeneratorJob.h" #include "ctkDICOMInserterJob.h" #include "ctkDICOMJobResponseSet.h" #include "ctkDICOMQueryJob.h" @@ -114,6 +115,7 @@ ctkDICOMSchedulerPrivate::ctkDICOMSchedulerPrivate(ctkDICOMScheduler& obj) OFunique_ptr layout(new dcmtk::log4cplus::PatternLayout("%D{%Y-%m-%d %H:%M:%S.%q} %5p: %m%n")); this->Appender = (new ctkDICOMJobsAppender()); + this->Appender->setName("ctkDICOM"); this->Appender->setLayout(OFmove(layout)); this->Appender->setThreshold(ctk::dicomLogLevel()); @@ -147,6 +149,10 @@ bool ctkDICOMSchedulerPrivate::isServerAllowed(ctkDICOMServer *server, { return false; } + else if (server->trustedEnabled()) + { + return true; + } else if (allowedSeversForPatient.contains(server->connectionName())) { return true; @@ -172,6 +178,45 @@ ctkDICOMServer* ctkDICOMSchedulerPrivate::getServerFromProxyServersByConnectionN return nullptr; } +//------------------------------------------------------------------------------ +bool ctkDICOMSchedulerPrivate::isJobDuplicate(ctkDICOMJob *referenceJob) +{ + bool duplicate = false; + { + // The QMutexLocker is enclosed within brackets to restrict its scope and + // prevent conflicts with other QMutexLockers within the scheduler's methods. + QMutexLocker locker(&this->QueueMutex); + foreach (QSharedPointer job, this->JobsQueue) + { + if (!job) + { + continue; + } + + ctkDICOMJob* dicomJob = qobject_cast(job.data()); + if (!dicomJob) + { + logger.debug("ctkDICOMScheduler::getJobsByDICOMUIDs: unexpected type of job."); + continue; + } + + if (dicomJob->className() == referenceJob->className() && + dicomJob->patientID() == referenceJob->patientID() && + dicomJob->studyInstanceUID() == referenceJob->studyInstanceUID() && + dicomJob->seriesInstanceUID() == referenceJob->seriesInstanceUID() && + dicomJob->sopInstanceUID() == referenceJob->sopInstanceUID() && + dicomJob->dicomLevel() == referenceJob->dicomLevel() && + dicomJob->status() < ctkAbstractJob::JobStatus::UserStopped) + { + duplicate = true; + break; + } + } + } + + return duplicate; +} + //------------------------------------------------------------------------------ // ctkDICOMScheduler methods @@ -461,6 +506,37 @@ void ctkDICOMScheduler::echo(ctkDICOMServer &server, QThread::Priority priority) d->insertJob(job); } +//---------------------------------------------------------------------------- +void ctkDICOMScheduler::generateThumbnail(const QString &originalFilePath, + const QString &patientID, + const QString &studyInstanceUID, + const QString &seriesInstanceUID, + const QString &sopInstanceUID, + const QString &modality, + QColor backgroundColor, + QThread::Priority priority) +{ + Q_D(ctkDICOMScheduler); + + QSharedPointer job = + QSharedPointer(new ctkDICOMThumbnailGeneratorJob); + job->setDatabaseFilename(d->DicomDatabase->databaseFilename()); + job->setDicomFilePath(originalFilePath); + job->setModality(modality); + job->setBackgroundColor(backgroundColor); + job->setPatientID(patientID); + job->setStudyInstanceUID(studyInstanceUID); + job->setSeriesInstanceUID(seriesInstanceUID); + job->setSOPInstanceUID(sopInstanceUID); + job->setMaximumNumberOfRetry(0); + job->setPriority(priority); + + if (!d->isJobDuplicate(job.data())) + { + d->insertJob(job); + } +} + //---------------------------------------------------------------------------- QString ctkDICOMScheduler::insertJobResponseSet(const QSharedPointer& jobResponseSet, QThread::Priority priority) @@ -607,6 +683,11 @@ void ctkDICOMScheduler::addServer(ctkDICOMServer& server) Q_D(ctkDICOMScheduler); QSharedPointer QSharedServer = QSharedPointer(&server, skipDelete); d->Servers.append(QSharedServer); + + QMetaObject::Connection serverModifiedConnection = + this->connect(QSharedServer.data(), SIGNAL(serverModified(QString)), + this, SIGNAL(serverModified(QString))); + d->ServersConnections.insert(QSharedServer->connectionName(), serverModifiedConnection); } //---------------------------------------------------------------------------- @@ -614,6 +695,11 @@ void ctkDICOMScheduler::addServer(QSharedPointer server) { Q_D(ctkDICOMScheduler); d->Servers.append(server); + + QMetaObject::Connection serverModifiedConnection = + this->connect(server.data(), SIGNAL(serverModified(QString)), + this, SIGNAL(serverModified(QString))); + d->ServersConnections.insert(server->connectionName(), serverModifiedConnection); } //---------------------------------------------------------------------------- @@ -631,6 +717,15 @@ void ctkDICOMScheduler::removeNthServer(int id) return; } + ctkDICOMServer* server = this->getNthServer(id); + if (!server) + { + return; + } + + QMetaObject::Connection connection = d->ServersConnections.value(server->connectionName()); + QObject::disconnect(connection); + d->Servers.removeAt(id); } @@ -639,6 +734,12 @@ void ctkDICOMScheduler::removeAllServers() { Q_D(ctkDICOMScheduler); d->Servers.clear(); + for (QMap::iterator it = d->ServersConnections.begin(); + it != d->ServersConnections.end(); ++it) + { + QObject::disconnect(it.value()); + } + d->ServersConnections.clear(); } //---------------------------------------------------------------------------- @@ -912,86 +1013,6 @@ void ctkDICOMScheduler::stopJobsByDICOMUIDs(const QStringList& patientIDs, this->stopJobsByJobUIDs(jobsUIDs); } -//---------------------------------------------------------------------------- -void ctkDICOMScheduler::runJob(const ctkDICOMJobDetail& jd, const QStringList& allowedSeversForPatient) -{ - QStringList allowedSevers = QStringList(jd.ConnectionName); - allowedSevers.append(allowedSeversForPatient); - if (jd.JobClass == "ctkDICOMQueryJob") - { - switch (jd.DICOMLevel) - { - case ctkDICOMJob::DICOMLevels::None: - logger.warn("ctkDICOMScheduler : DICOMLevels was not set."); - break; - case ctkDICOMJob::DICOMLevels::Patients: - this->queryPatients(); - break; - case ctkDICOMJob::DICOMLevels::Studies: - this->queryStudies(jd.PatientID, - QThread::NormalPriority, - allowedSevers); - break; - case ctkDICOMJob::DICOMLevels::Series: - this->querySeries(jd.PatientID, - jd.StudyInstanceUID, - QThread::NormalPriority, - allowedSevers); - break; - case ctkDICOMJob::DICOMLevels::Instances: - this->queryInstances(jd.PatientID, - jd.StudyInstanceUID, - jd.SeriesInstanceUID, - QThread::NormalPriority, - allowedSevers); - break; - } - } - else if (jd.JobClass == "ctkDICOMRetrieveJob") - { - switch (jd.DICOMLevel) - { - case ctkDICOMJob::DICOMLevels::None: - logger.warn("ctkDICOMScheduler : DICOMLevels was not set."); - break; - case ctkDICOMJob::DICOMLevels::Patients: - logger.warn("Retrieve Patient is not implemented"); - break; - case ctkDICOMJob::DICOMLevels::Studies: - this->retrieveStudy(jd.PatientID, - jd.StudyInstanceUID, - QThread::NormalPriority, - allowedSevers); - break; - case ctkDICOMJob::DICOMLevels::Series: - this->retrieveSeries(jd.PatientID, - jd.StudyInstanceUID, - jd.SeriesInstanceUID, - QThread::NormalPriority, - allowedSevers); - break; - case ctkDICOMJob::DICOMLevels::Instances: - this->retrieveSOPInstance(jd.PatientID, - jd.StudyInstanceUID, - jd.SeriesInstanceUID, - jd.SOPInstanceUID, - QThread::NormalPriority, - allowedSevers); - break; - } - } -} - -//---------------------------------------------------------------------------- -void ctkDICOMScheduler::runJobs(const QMap &jobDetails) -{ - for(QString jobUID : jobDetails.keys()) - { - ctkDICOMJobDetail jd = jobDetails.value(jobUID); - this->runJob(jd); - } -} - //---------------------------------------------------------------------------- void ctkDICOMScheduler::raiseJobsPriorityForSeries(const QStringList& selectedSeriesInstanceUIDs, QThread::Priority priority) @@ -1095,7 +1116,7 @@ void ctkDICOMScheduler::onJobStarted(ctkAbstractJob* job) if (appender) { QString loggedText = appender->messageByThreadID(job->runningThreadID()); - job->setLoggedText(loggedText); + job->addLoggedText(loggedText); } ctkJobScheduler::onJobStarted(job); @@ -1114,7 +1135,7 @@ void ctkDICOMScheduler::onJobUserStopped(ctkAbstractJob* job) if (appender) { QString loggedText = appender->messageByThreadID(job->runningThreadID()); - job->setLoggedText(loggedText); + job->addLoggedText(loggedText); } ctkJobScheduler::onJobUserStopped(job); @@ -1133,7 +1154,7 @@ void ctkDICOMScheduler::onJobFinished(ctkAbstractJob* job) if (appender) { QString loggedText = appender->messageByThreadID(job->runningThreadID()); - job->setLoggedText(loggedText); + job->addLoggedText(loggedText); } ctkJobScheduler::onJobFinished(job); @@ -1152,7 +1173,7 @@ void ctkDICOMScheduler::onJobAttemptFailed(ctkAbstractJob* job) if (appender) { QString loggedText = appender->messageByThreadID(job->runningThreadID()); - job->setLoggedText(loggedText); + job->addLoggedText(loggedText); } ctkJobScheduler::onJobAttemptFailed(job); @@ -1171,7 +1192,7 @@ void ctkDICOMScheduler::onJobFailed(ctkAbstractJob* job) if (appender) { QString loggedText = appender->messageByThreadID(job->runningThreadID()); - job->setLoggedText(loggedText); + job->addLoggedText(loggedText); } ctkJobScheduler::onJobFailed(job); diff --git a/Libs/DICOM/Core/ctkDICOMScheduler.h b/Libs/DICOM/Core/ctkDICOMScheduler.h index 3409f86a77..9ea77d996c 100644 --- a/Libs/DICOM/Core/ctkDICOMScheduler.h +++ b/Libs/DICOM/Core/ctkDICOMScheduler.h @@ -113,6 +113,16 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMScheduler : public ctkJobScheduler Q_INVOKABLE void echo(ctkDICOMServer& server, QThread::Priority priority = QThread::LowPriority); + /// Generate thumbnail and save it as png on local disk + Q_INVOKABLE void generateThumbnail(const QString &originalFilePath, + const QString &patientID, + const QString &studyInstanceUID, + const QString &seriesInstanceUID, + const QString &sopInstanceUID, + const QString& modality, + QColor backgroundColor, + QThread::Priority priority = QThread::HighPriority); + ///@{ /// Insert results from a job QString insertJobResponseSet(const QSharedPointer& jobResponseSet, @@ -186,9 +196,6 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMScheduler : public ctkJobScheduler const QStringList& studyInstanceUIDs = {}, const QStringList& seriesInstanceUIDs = {}, const QStringList& sopInstanceUIDs = {}); - - Q_INVOKABLE void runJob(const ctkDICOMJobDetail& jobDetails, const QStringList& allowedSeversForPatient = QStringList()); - Q_INVOKABLE void runJobs(const QMap& jobDetails); Q_INVOKABLE void raiseJobsPriorityForSeries(const QStringList& selectedSeriesInstanceUIDs, QThread::Priority priority = QThread::HighestPriority); ///@} @@ -213,6 +220,10 @@ public Q_SLOTS: virtual void onJobAttemptFailed(ctkAbstractJob* job); virtual void onJobFailed(ctkAbstractJob* job); +Q_SIGNALS: + /// Emitted when a server is modified + void serverModified(const QString&); + protected: ctkDICOMScheduler(ctkDICOMSchedulerPrivate* pimpl, QObject* parent); diff --git a/Libs/DICOM/Core/ctkDICOMScheduler_p.h b/Libs/DICOM/Core/ctkDICOMScheduler_p.h index 7762db7cf8..5b2f493728 100644 --- a/Libs/DICOM/Core/ctkDICOMScheduler_p.h +++ b/Libs/DICOM/Core/ctkDICOMScheduler_p.h @@ -61,9 +61,11 @@ class ctkDICOMSchedulerPrivate : public ctkJobSchedulerPrivate bool isServerAllowed(ctkDICOMServer* server, const QStringList& allowedSeversForPatient); ctkDICOMServer* getServerFromProxyServersByConnectionName(const QString&); + bool isJobDuplicate(ctkDICOMJob* job); QSharedPointer DicomDatabase; QList> Servers; + QMap ServersConnections; QMap Filters; int MaximumPatientsQuery{25}; diff --git a/Libs/DICOM/Core/ctkDICOMServer.cpp b/Libs/DICOM/Core/ctkDICOMServer.cpp index 488bec46f5..a238eb67f0 100644 --- a/Libs/DICOM/Core/ctkDICOMServer.cpp +++ b/Libs/DICOM/Core/ctkDICOMServer.cpp @@ -106,36 +106,95 @@ ctkDICOMServer::ctkDICOMServer(QObject* parent) ctkDICOMServer::~ctkDICOMServer() = default; //------------------------------------------------------------------------------ -CTK_SET_CPP(ctkDICOMServer, const QString&, setConnectionName, ConnectionName); CTK_GET_CPP(ctkDICOMServer, QString, connectionName, ConnectionName) -CTK_SET_CPP(ctkDICOMServer, const bool&, setQueryRetrieveEnabled, QueryRetrieveEnabled); CTK_GET_CPP(ctkDICOMServer, bool, queryRetrieveEnabled, QueryRetrieveEnabled) -CTK_SET_CPP(ctkDICOMServer, const bool&, setStorageEnabled, StorageEnabled); CTK_GET_CPP(ctkDICOMServer, bool, storageEnabled, StorageEnabled) -CTK_SET_CPP(ctkDICOMServer, const bool&, setTrustedEnabled, TrustedEnabled); CTK_GET_CPP(ctkDICOMServer, bool, trustedEnabled, TrustedEnabled) -CTK_SET_CPP(ctkDICOMServer, const QString&, setCallingAETitle, CallingAETitle); CTK_GET_CPP(ctkDICOMServer, QString, callingAETitle, CallingAETitle) -CTK_SET_CPP(ctkDICOMServer, const QString&, setCalledAETitle, CalledAETitle); CTK_GET_CPP(ctkDICOMServer, QString, calledAETitle, CalledAETitle) -CTK_SET_CPP(ctkDICOMServer, const QString&, setHost, Host); CTK_GET_CPP(ctkDICOMServer, QString, host, Host) -CTK_SET_CPP(ctkDICOMServer, const int&, setPort, Port); CTK_GET_CPP(ctkDICOMServer, int, port, Port) -CTK_SET_CPP(ctkDICOMServer, RetrieveProtocol, setRetrieveProtocol, RetrieveProtocol); CTK_GET_CPP(ctkDICOMServer, ctkDICOMServer::RetrieveProtocol, retrieveProtocol, RetrieveProtocol) -CTK_SET_CPP(ctkDICOMServer, const QString&, setMoveDestinationAETitle, MoveDestinationAETitle); CTK_GET_CPP(ctkDICOMServer, QString, moveDestinationAETitle, MoveDestinationAETitle) -CTK_SET_CPP(ctkDICOMServer, const bool&, setKeepAssociationOpen, KeepAssociationOpen); CTK_GET_CPP(ctkDICOMServer, bool, keepAssociationOpen, KeepAssociationOpen) -CTK_SET_CPP(ctkDICOMServer, const int&, setConnectionTimeout, ConnectionTimeout); CTK_GET_CPP(ctkDICOMServer, int, connectionTimeout, ConnectionTimeout) //------------------------------------------------------------------------------ -void ctkDICOMServer::setRetrieveProtocolAsString(const QString& protocolString) +void ctkDICOMServer::setConnectionName(const QString& connectionName) +{ + Q_D(ctkDICOMServer); + d->ConnectionName = connectionName; + emit serverModified(d->ConnectionName); +} + +//------------------------------------------------------------------------------ +void ctkDICOMServer::setQueryRetrieveEnabled(const bool& queryRetrieveEnabled) +{ + Q_D(ctkDICOMServer); + d->QueryRetrieveEnabled = queryRetrieveEnabled; + emit serverModified(d->ConnectionName); +} + +//------------------------------------------------------------------------------ +void ctkDICOMServer::setStorageEnabled(const bool& storageEnabled) { Q_D(ctkDICOMServer); + d->StorageEnabled = storageEnabled; + emit serverModified(d->ConnectionName); +} +//------------------------------------------------------------------------------ +void ctkDICOMServer::setTrustedEnabled(const bool& trustedEnabled) +{ + Q_D(ctkDICOMServer); + d->TrustedEnabled = trustedEnabled; + emit serverModified(d->ConnectionName); +} + +//------------------------------------------------------------------------------ +void ctkDICOMServer::setCallingAETitle(const QString& callingAETitle) +{ + Q_D(ctkDICOMServer); + d->CallingAETitle = callingAETitle; + emit serverModified(d->ConnectionName); +} + +//------------------------------------------------------------------------------ +void ctkDICOMServer::setCalledAETitle(const QString& calledAETitle) +{ + Q_D(ctkDICOMServer); + d->CalledAETitle = calledAETitle; + emit serverModified(d->ConnectionName); +} + +//------------------------------------------------------------------------------ +void ctkDICOMServer::setHost(const QString& host) +{ + Q_D(ctkDICOMServer); + d->Host = host; + emit serverModified(d->ConnectionName); +} + +//------------------------------------------------------------------------------ +void ctkDICOMServer::setPort(const int& port) +{ + Q_D(ctkDICOMServer); + d->Port = port; + emit serverModified(d->ConnectionName); +} + +//------------------------------------------------------------------------------ +void ctkDICOMServer::setRetrieveProtocol(RetrieveProtocol protocol) +{ + Q_D(ctkDICOMServer); + d->RetrieveProtocol = protocol; + emit serverModified(d->ConnectionName); +} + +//------------------------------------------------------------------------------ +void ctkDICOMServer::setRetrieveProtocolAsString(const QString& protocolString) +{ + Q_D(ctkDICOMServer); if (protocolString == "CGET") { d->RetrieveProtocol = RetrieveProtocol::CGET; @@ -148,6 +207,7 @@ void ctkDICOMServer::setRetrieveProtocolAsString(const QString& protocolString) { d->RetrieveProtocol = RetrieveProtocol::WADO; }*/ + emit serverModified(d->ConnectionName); } //------------------------------------------------------------------------------ @@ -174,6 +234,30 @@ QString ctkDICOMServer::retrieveProtocolAsString() const return protocolString; } +//------------------------------------------------------------------------------ +void ctkDICOMServer::setMoveDestinationAETitle(const QString& moveDestinationAETitle) +{ + Q_D(ctkDICOMServer); + d->MoveDestinationAETitle = moveDestinationAETitle; + emit serverModified(d->ConnectionName); +} + +//------------------------------------------------------------------------------ +void ctkDICOMServer::setKeepAssociationOpen(const bool& keepAssociationOpen) +{ + Q_D(ctkDICOMServer); + d->KeepAssociationOpen = keepAssociationOpen; + emit serverModified(d->ConnectionName); +} + +//------------------------------------------------------------------------------ +void ctkDICOMServer::setConnectionTimeout(const int& timeout) +{ + Q_D(ctkDICOMServer); + d->ConnectionTimeout = timeout; + emit serverModified(d->ConnectionName); +} + //---------------------------------------------------------------------------- ctkDICOMServer* ctkDICOMServer::proxyServer() const { @@ -186,6 +270,7 @@ void ctkDICOMServer::setProxyServer(const ctkDICOMServer& proxyServer) { Q_D(ctkDICOMServer); d->ProxyServer = proxyServer.clone(); + emit serverModified(d->ConnectionName); } //---------------------------------------------------------------------------- diff --git a/Libs/DICOM/Core/ctkDICOMServer.h b/Libs/DICOM/Core/ctkDICOMServer.h index 9e654e47a2..8331289850 100644 --- a/Libs/DICOM/Core/ctkDICOMServer.h +++ b/Libs/DICOM/Core/ctkDICOMServer.h @@ -152,6 +152,10 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMServer : public QObject /// Create a copy of this Server. Q_INVOKABLE ctkDICOMServer* clone() const; +Q_SIGNALS: + /// Emitted when a server is modified + void serverModified(const QString&); + protected: QScopedPointer d_ptr; diff --git a/Libs/DICOM/Core/ctkDICOMStorageListener.cpp b/Libs/DICOM/Core/ctkDICOMStorageListener.cpp index 46b5481aa9..98e2fdaae3 100644 --- a/Libs/DICOM/Core/ctkDICOMStorageListener.cpp +++ b/Libs/DICOM/Core/ctkDICOMStorageListener.cpp @@ -24,9 +24,6 @@ #include #include -// ctkCore includes -#include - // ctkDICOMCore includes #include "ctkDICOMJobResponseSet.h" #include "ctkDICOMStorageListener.h" @@ -34,7 +31,7 @@ // DCMTK includes #include /* for DcmStorageSCP */ -static ctkLogger logger ( "org.commontk.dicom.DICOMStorageListener" ); +dcmtk::log4cplus::Logger rootLogStorageListener = dcmtk::log4cplus::Logger::getRoot(); //------------------------------------------------------------------------------ // A customized implementation so that Qt signals can be emitted @@ -226,7 +223,8 @@ QString ctkDICOMStorageListenerPrivate::defaultConfigFile() const } else { - logger.error("Failed to find listener configuration file"); + QString error = ctkDICOMStorageListener::tr("Failed to find listener configuration file"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogStorageListener, error.toStdString().c_str()); return ""; } @@ -243,7 +241,8 @@ QString ctkDICOMStorageListenerPrivate::defaultConfigFile() const } else { - logger.error("Failed to find listener configuration file"); + QString error = ctkDICOMStorageListener::tr("Failed to find listener configuration file"); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogStorageListener, error.toStdString().c_str()); return ""; } writeFile.close(); @@ -290,9 +289,10 @@ bool ctkDICOMStorageListener::listen() OFCondition status = d->SCU.listen(); if (status.bad() || d->Canceled) { - logger.error(QString("SCP stopped, it was listening on port %1 : %2 ") - .arg(QString::number(d->Port)) - .arg(status.text())); + QString error = ctkDICOMStorageListener::tr("SCP stopped, it was listening on port %1 : %2 ") + .arg(QString::number(d->Port)) + .arg(status.text()); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogStorageListener, error.toStdString().c_str()); return false; } return true; @@ -324,7 +324,8 @@ bool ctkDICOMStorageListener::initializeSCU() OFString(d->defaultConfigFile().toStdString().c_str()), "alldicom"); if (status.bad()) { - logger.error(QString("Cannot load association configuration: %1").arg(status.text())); + QString error = ctkDICOMStorageListener::tr("Cannot load association configuration: %1").arg(status.text()); + DCMTK_LOG4CPLUS_ERROR_STR(rootLogStorageListener, error.toStdString().c_str()); return false; } diff --git a/Libs/DICOM/Core/ctkDICOMStorageListenerJob.cpp b/Libs/DICOM/Core/ctkDICOMStorageListenerJob.cpp index 9a966f29ab..d3df92083d 100644 --- a/Libs/DICOM/Core/ctkDICOMStorageListenerJob.cpp +++ b/Libs/DICOM/Core/ctkDICOMStorageListenerJob.cpp @@ -73,12 +73,19 @@ CTK_SET_CPP(ctkDICOMStorageListenerJob, const QString&, setAETitle, AETitle); CTK_GET_CPP(ctkDICOMStorageListenerJob, QString, AETitle, AETitle) //---------------------------------------------------------------------------- -QString ctkDICOMStorageListenerJob::loggerReport(const QString& status) const +QString ctkDICOMStorageListenerJob::loggerReport(const QString& status) { - return QString("ctkDICOMStorageListenerJob: listener job %1.\n" - "JobUID: %2\n") - .arg(status) - .arg(this->jobUID()); + QString fullLogMsg = QString("ctkDICOMStorageListenerJob: listener job %1.\n" + "JobUID: %2\n") + .arg(status) + .arg(this->jobUID()); + QString logMsg = QString("Listener job %1.\n") + .arg(status); + QString currentDateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss.zzz"); + QString logHeader = currentDateTime + " INFO: "; + this->LoggedText += logHeader; + this->LoggedText += logMsg; + return fullLogMsg; } //------------------------------------------------------------------------------ ctkAbstractJob* ctkDICOMStorageListenerJob::clone() const diff --git a/Libs/DICOM/Core/ctkDICOMStorageListenerJob.h b/Libs/DICOM/Core/ctkDICOMStorageListenerJob.h index c6d7018853..1bf1a8b426 100644 --- a/Libs/DICOM/Core/ctkDICOMStorageListenerJob.h +++ b/Libs/DICOM/Core/ctkDICOMStorageListenerJob.h @@ -69,7 +69,7 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMStorageListenerJob : public ctkDICOMJob ///@} /// Logger report string formatting for specific task - Q_INVOKABLE QString loggerReport(const QString& status) const override; + Q_INVOKABLE QString loggerReport(const QString& status) override; /// \see ctkAbstractJob::clone() Q_INVOKABLE ctkAbstractJob* clone() const override; diff --git a/Libs/DICOM/Widgets/ctkDICOMThumbnailGenerator.cpp b/Libs/DICOM/Core/ctkDICOMThumbnailGenerator.cpp similarity index 90% rename from Libs/DICOM/Widgets/ctkDICOMThumbnailGenerator.cpp rename to Libs/DICOM/Core/ctkDICOMThumbnailGenerator.cpp index c57819d6c7..579254f581 100644 --- a/Libs/DICOM/Widgets/ctkDICOMThumbnailGenerator.cpp +++ b/Libs/DICOM/Core/ctkDICOMThumbnailGenerator.cpp @@ -21,7 +21,6 @@ // ctkDICOMCore includes #include "ctkDICOMThumbnailGenerator.h" -#include "ctkLogger.h" // Qt includes #include @@ -33,7 +32,7 @@ // DCMTK includes #include "dcmtk/dcmimgle/dcmimage.h" -static ctkLogger logger ( "org.commontk.dicom.DICOMThumbnailGenerator" ); +dcmtk::log4cplus::Logger rootLogThumbnailGenerator = dcmtk::log4cplus::Logger::getRoot(); //------------------------------------------------------------------------------ class ctkDICOMThumbnailGeneratorPrivate @@ -133,7 +132,8 @@ bool ctkDICOMThumbnailGenerator::generateThumbnail(DicomImage *dcmImage, QImage& EI_Status result = dcmImage->getStatus(); if (result != EIS_Normal) { - logger.warn(QString("Rendering of DICOM image failed for thumbnail failed: ") + DicomImage::getString(result)); + QString warn = ctkDICOMThumbnailGenerator::tr("Rendering of DICOM image failed for thumbnail failed: ") + DicomImage::getString(result); + DCMTK_LOG4CPLUS_WARN_STR(rootLogThumbnailGenerator, warn.toStdString().c_str()); return false; } // Select first window defined in image. If none, compute min/max window as best guess. @@ -191,7 +191,8 @@ bool ctkDICOMThumbnailGenerator::generateThumbnail(DicomImage *dcmImage, QImage& } //------------------------------------------------------------------------------ -bool ctkDICOMThumbnailGenerator::generateThumbnail(DicomImage *dcmImage, const QString &thumbnailPath, QVector color) +bool ctkDICOMThumbnailGenerator::generateThumbnail(DicomImage *dcmImage, const QString &thumbnailPath, + QColor backgroundColor) { QImage image; if (this->generateThumbnail(dcmImage, image)) @@ -199,7 +200,7 @@ bool ctkDICOMThumbnailGenerator::generateThumbnail(DicomImage *dcmImage, const Q return image.save(thumbnailPath, "PNG"); } - this->generateDocumentThumbnail(thumbnailPath, color); + this->generateDocumentThumbnail(thumbnailPath, backgroundColor); return false; } @@ -218,21 +219,22 @@ bool ctkDICOMThumbnailGenerator::generateThumbnail(const QString& dcmImagePath, } //------------------------------------------------------------------------------ -void ctkDICOMThumbnailGenerator::generateBlankThumbnail(QImage& image, QColor color) +void ctkDICOMThumbnailGenerator::generateBlankThumbnail(QImage& image, QColor backgroundColor) { Q_D(ctkDICOMThumbnailGenerator); if (image.width() != d->Width || image.height() != d->Height) { image = QImage(d->Width, d->Height, QImage::Format_RGB32); } - image.fill(color); + image.fill(backgroundColor); } //------------------------------------------------------------------------------ -void ctkDICOMThumbnailGenerator::generateDocumentThumbnail(const QString &thumbnailPath, QVector color) +void ctkDICOMThumbnailGenerator::generateDocumentThumbnail(const QString &thumbnailPath, + QColor backgroundColor) { QImage image; - this->generateBlankThumbnail(image, QColor(color[0], color[1], color[2])); + this->generateBlankThumbnail(image, backgroundColor); QPixmap pixmap = QPixmap::fromImage(image); QPainter painter; if (painter.begin(&pixmap)) diff --git a/Libs/DICOM/Widgets/ctkDICOMThumbnailGenerator.h b/Libs/DICOM/Core/ctkDICOMThumbnailGenerator.h similarity index 88% rename from Libs/DICOM/Widgets/ctkDICOMThumbnailGenerator.h rename to Libs/DICOM/Core/ctkDICOMThumbnailGenerator.h index 6881972bfb..b00f99b400 100644 --- a/Libs/DICOM/Widgets/ctkDICOMThumbnailGenerator.h +++ b/Libs/DICOM/Core/ctkDICOMThumbnailGenerator.h @@ -28,16 +28,16 @@ class QImage; // ctkDICOMWidgets includes #include "ctkDICOMAbstractThumbnailGenerator.h" -#include "ctkDICOMWidgetsExport.h" +#include "ctkDICOMCoreExport.h" class ctkDICOMThumbnailGeneratorPrivate; // DCMTK includes class DicomImage; -/// \ingroup DICOM_Widgets +/// \ingroup DICOM_Core /// /// \brief Thumbnail generator class -class CTK_DICOM_WIDGETS_EXPORT ctkDICOMThumbnailGenerator : public ctkDICOMAbstractThumbnailGenerator +class CTK_DICOM_CORE_EXPORT ctkDICOMThumbnailGenerator : public ctkDICOMAbstractThumbnailGenerator { Q_OBJECT Q_PROPERTY(int width READ width WRITE setWidth) @@ -50,7 +50,7 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMThumbnailGenerator : public ctkDICOMAbstr virtual ~ctkDICOMThumbnailGenerator(); virtual bool generateThumbnail(DicomImage* dcmImage, const QString& thumbnailPath, - QVector color = QVector{169, 169, 169}); + QColor backgroundColor = Qt::darkGray); Q_INVOKABLE bool generateThumbnail(DicomImage *dcmImage, QImage& image); Q_INVOKABLE bool generateThumbnail(const QString& dcmImagePath, QImage& image); @@ -58,9 +58,9 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMThumbnailGenerator : public ctkDICOMAbstr /// Generate a blank thumbnail image (currently a solid gray box of the requested thumbnail size). /// It can be used as a placeholder for invalid images or duringan image is loaded. - Q_INVOKABLE void generateBlankThumbnail(QImage& image, QColor color = Qt::darkGray); + Q_INVOKABLE void generateBlankThumbnail(QImage& image, QColor backgroundColor = Qt::darkGray); Q_INVOKABLE virtual void generateDocumentThumbnail(const QString &thumbnailPath, - QVector color = QVector{169, 169, 169}); + QColor backgroundColor = Qt::darkGray); /// Set thumbnail width void setWidth(int width); diff --git a/Libs/DICOM/Core/ctkDICOMThumbnailGeneratorJob.cpp b/Libs/DICOM/Core/ctkDICOMThumbnailGeneratorJob.cpp new file mode 100644 index 0000000000..269db0fa2d --- /dev/null +++ b/Libs/DICOM/Core/ctkDICOMThumbnailGeneratorJob.cpp @@ -0,0 +1,129 @@ +/*========================================================================= + + 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). + +=========================================================================*/ + +// ctkCore includes +#include + +// ctkDICOMCore includes +#include "ctkDICOMJobResponseSet.h" // For ctkDICOMJobDetail +#include "ctkDICOMThumbnailGeneratorJob_p.h" +#include "ctkDICOMThumbnailGeneratorWorker.h" + +static ctkLogger logger ( "org.commontk.dicom.DICOMThumbnailGeneratorJob" ); + +//------------------------------------------------------------------------------ +// ctkDICOMThumbnailGeneratorJobPrivate methods + +//------------------------------------------------------------------------------ +ctkDICOMThumbnailGeneratorJobPrivate::ctkDICOMThumbnailGeneratorJobPrivate(ctkDICOMThumbnailGeneratorJob* object) + : q_ptr(object) +{ + this->DatabaseFilename = ""; + this->DicomFilePath = ""; + this->Modality = ""; + this->BackgroundColor = Qt::darkGray; +} + +//------------------------------------------------------------------------------ +ctkDICOMThumbnailGeneratorJobPrivate::~ctkDICOMThumbnailGeneratorJobPrivate() +{ +} + +//------------------------------------------------------------------------------ +CTK_GET_CPP(ctkDICOMThumbnailGeneratorJob, QString, databaseFilename, DatabaseFilename); +CTK_SET_CPP(ctkDICOMThumbnailGeneratorJob, QString, setDatabaseFilename, DatabaseFilename); +CTK_GET_CPP(ctkDICOMThumbnailGeneratorJob, QString, dicomFilePath, DicomFilePath); +CTK_SET_CPP(ctkDICOMThumbnailGeneratorJob, QString, setDicomFilePath, DicomFilePath); +CTK_GET_CPP(ctkDICOMThumbnailGeneratorJob, QString, modality, Modality); +CTK_SET_CPP(ctkDICOMThumbnailGeneratorJob, QString, setModality, Modality); +CTK_GET_CPP(ctkDICOMThumbnailGeneratorJob, QColor, backgroundColor, BackgroundColor); +CTK_SET_CPP(ctkDICOMThumbnailGeneratorJob, QColor, setBackgroundColor, BackgroundColor); + +//------------------------------------------------------------------------------ +// ctkDICOMThumbnailGeneratorJob methods + +//------------------------------------------------------------------------------ +ctkDICOMThumbnailGeneratorJob::ctkDICOMThumbnailGeneratorJob() + : d_ptr(new ctkDICOMThumbnailGeneratorJobPrivate(this)) +{ +} + +//------------------------------------------------------------------------------ +ctkDICOMThumbnailGeneratorJob::ctkDICOMThumbnailGeneratorJob(ctkDICOMThumbnailGeneratorJobPrivate* pimpl) + : d_ptr(pimpl) +{ +} + +//------------------------------------------------------------------------------ +ctkDICOMThumbnailGeneratorJob::~ctkDICOMThumbnailGeneratorJob() = default; + +//---------------------------------------------------------------------------- +QString ctkDICOMThumbnailGeneratorJob::loggerReport(const QString& status) +{ + QString fullLogMsg = QString("ctkDICOMThumbnailGeneratorJob: thumbnail generator job %1.\n") + .arg(status); + QString logMsg = QString("Thumbnail generator job %1.\n") + .arg(status); + QString currentDateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss.zzz"); + QString logHeader = currentDateTime + " INFO: "; + this->LoggedText += logHeader; + this->LoggedText += logMsg; + return fullLogMsg; +} +//------------------------------------------------------------------------------ +ctkAbstractJob* ctkDICOMThumbnailGeneratorJob::clone() const +{ + ctkDICOMThumbnailGeneratorJob* newThumbnailGeneratorJob = new ctkDICOMThumbnailGeneratorJob; + newThumbnailGeneratorJob->setMaximumNumberOfRetry(this->maximumNumberOfRetry()); + newThumbnailGeneratorJob->setRetryDelay(this->retryDelay()); + newThumbnailGeneratorJob->setRetryCounter(this->retryCounter()); + newThumbnailGeneratorJob->setIsPersistent(this->isPersistent()); + newThumbnailGeneratorJob->setMaximumConcurrentJobsPerType(this->maximumConcurrentJobsPerType()); + newThumbnailGeneratorJob->setPriority(this->priority()); + newThumbnailGeneratorJob->setBackgroundColor(this->backgroundColor()); + newThumbnailGeneratorJob->setModality(this->modality()); + newThumbnailGeneratorJob->setDicomFilePath(this->dicomFilePath()); + + return newThumbnailGeneratorJob; +} + +//------------------------------------------------------------------------------ +ctkAbstractWorker* ctkDICOMThumbnailGeneratorJob::createWorker() +{ + ctkDICOMThumbnailGeneratorWorker* worker = + new ctkDICOMThumbnailGeneratorWorker; + worker->setJob(*this); + return worker; +} + +//------------------------------------------------------------------------------ +QVariant ctkDICOMThumbnailGeneratorJob::toVariant() +{ + return QVariant::fromValue(ctkDICOMJobDetail(*this)); +} + +//------------------------------------------------------------------------------ +ctkDICOMJobResponseSet::JobType ctkDICOMThumbnailGeneratorJob::getJobType() const +{ + return ctkDICOMJobResponseSet::JobType::ThumbnailGenerator; +} diff --git a/Libs/DICOM/Core/ctkDICOMThumbnailGeneratorJob.h b/Libs/DICOM/Core/ctkDICOMThumbnailGeneratorJob.h new file mode 100644 index 0000000000..3636cf800f --- /dev/null +++ b/Libs/DICOM/Core/ctkDICOMThumbnailGeneratorJob.h @@ -0,0 +1,112 @@ +/*========================================================================= + + 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 __ctkDICOMThumbnailGeneratorJob_h +#define __ctkDICOMThumbnailGeneratorJob_h + +// Qt includes +#include +#include +#include + +// ctkCore includes +class ctkAbstractWorker; + +// ctkDICOMCore includes +#include "ctkDICOMCoreExport.h" +#include "ctkDICOMJob.h" +class ctkDICOMThumbnailGeneratorJobPrivate; + +/// \ingroup DICOM_Core +class CTK_DICOM_CORE_EXPORT ctkDICOMThumbnailGeneratorJob : public ctkDICOMJob +{ + Q_OBJECT + Q_PROPERTY(QString databaseFilename READ databaseFilename WRITE setDatabaseFilename); + Q_PROPERTY(QString dicomFilePath READ dicomFilePath WRITE setDicomFilePath); + Q_PROPERTY(QString modality READ modality WRITE setModality); + Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor); + +public: + typedef ctkDICOMJob Superclass; + explicit ctkDICOMThumbnailGeneratorJob(); + virtual ~ctkDICOMThumbnailGeneratorJob(); + + ///@{ + /// Database Filename + void setDatabaseFilename(QString databaseFilename); + QString databaseFilename() const; + ///}@ + + ///@{ + /// Dicom file path + void setDicomFilePath(QString dicomFilePath); + QString dicomFilePath() const; + ///@} + + ///@{ + /// Modality + void setModality(QString modality); + QString modality() const; + ///@} + + ///@{ + /// Background Color + void setBackgroundColor(QColor backgroundColor); + QColor backgroundColor() const; + ///@} + + /// Logger report string formatting for specific task + Q_INVOKABLE QString loggerReport(const QString& status) override; + + /// \see ctkAbstractJob::clone() + Q_INVOKABLE ctkAbstractJob* clone() const override; + + /// Generate worker for job + Q_INVOKABLE ctkAbstractWorker* createWorker() override; + + /// Return the QVariant value of this job. + /// + /// The value is set using the ctkDICOMJobDetail metatype and is used to pass + /// information between threads using Qt signals. + /// \sa ctkDICOMJobDetail + Q_INVOKABLE virtual QVariant toVariant() override; + + /// Return job type. + Q_INVOKABLE virtual ctkDICOMJobResponseSet::JobType getJobType() const override; + +protected: + QScopedPointer d_ptr; + + /// Constructor allowing derived class to specify a specialized pimpl. + /// + /// \note You are responsible to call init() in the constructor of + /// derived class. Doing so ensures the derived class is fully + /// instantiated in case virtual method are called within init() itself. + ctkDICOMThumbnailGeneratorJob(ctkDICOMThumbnailGeneratorJobPrivate* pimpl); + +private: + Q_DECLARE_PRIVATE(ctkDICOMThumbnailGeneratorJob); + Q_DISABLE_COPY(ctkDICOMThumbnailGeneratorJob); +}; + +#endif diff --git a/Libs/DICOM/Core/ctkDICOMThumbnailGeneratorJob_p.h b/Libs/DICOM/Core/ctkDICOMThumbnailGeneratorJob_p.h new file mode 100644 index 0000000000..e07bb78bb3 --- /dev/null +++ b/Libs/DICOM/Core/ctkDICOMThumbnailGeneratorJob_p.h @@ -0,0 +1,54 @@ +/*========================================================================= + + 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 __ctkDICOMThumbnailGeneratorJobPrivate_h +#define __ctkDICOMThumbnailGeneratorJobPrivate_h + +// Qt includes +#include +#include + +// ctkDICOMCore includes +#include "ctkDICOMThumbnailGeneratorJob.h" + +//------------------------------------------------------------------------------ +class ctkDICOMThumbnailGeneratorJobPrivate : public QObject +{ + Q_OBJECT + Q_DECLARE_PUBLIC(ctkDICOMThumbnailGeneratorJob) + +protected: + ctkDICOMThumbnailGeneratorJob* const q_ptr; + +public: + ctkDICOMThumbnailGeneratorJobPrivate(ctkDICOMThumbnailGeneratorJob* object); + virtual ~ctkDICOMThumbnailGeneratorJobPrivate(); + +public: + QString DatabaseFilename; + QString DicomFilePath; + QString Modality; + QColor BackgroundColor; +}; + +#endif diff --git a/Libs/DICOM/Core/ctkDICOMThumbnailGeneratorWorker.cpp b/Libs/DICOM/Core/ctkDICOMThumbnailGeneratorWorker.cpp new file mode 100644 index 0000000000..e053937117 --- /dev/null +++ b/Libs/DICOM/Core/ctkDICOMThumbnailGeneratorWorker.cpp @@ -0,0 +1,149 @@ +/*========================================================================= + + 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). + +=========================================================================*/ + +// ctkCore includes +#include + +// ctkDICOMCore includes +#include "ctkDICOMThumbnailGenerator.h" +#include "ctkDICOMThumbnailGeneratorWorker_p.h" +#include "ctkDICOMThumbnailGeneratorJob.h" +#include "ctkDICOMScheduler.h" + +static ctkLogger logger ("org.commontk.dicom.DICOMRetrieveWorker"); + +//------------------------------------------------------------------------------ +// ctkDICOMThumbnailGeneratorWorkerPrivate methods + +//------------------------------------------------------------------------------ +ctkDICOMThumbnailGeneratorWorkerPrivate::ctkDICOMThumbnailGeneratorWorkerPrivate(ctkDICOMThumbnailGeneratorWorker* object) + : q_ptr(object) +{ + this->wasCancelled = false; +} + +//------------------------------------------------------------------------------ +ctkDICOMThumbnailGeneratorWorkerPrivate::~ctkDICOMThumbnailGeneratorWorkerPrivate() = default; + +//------------------------------------------------------------------------------ +// ctkDICOMThumbnailGeneratorWorker methods + +//------------------------------------------------------------------------------ +ctkDICOMThumbnailGeneratorWorker::ctkDICOMThumbnailGeneratorWorker() + : d_ptr(new ctkDICOMThumbnailGeneratorWorkerPrivate(this)) +{ +} + +//------------------------------------------------------------------------------ +ctkDICOMThumbnailGeneratorWorker::ctkDICOMThumbnailGeneratorWorker(ctkDICOMThumbnailGeneratorWorkerPrivate* pimpl) + : d_ptr(pimpl) +{ +} + +//------------------------------------------------------------------------------ +ctkDICOMThumbnailGeneratorWorker::~ctkDICOMThumbnailGeneratorWorker() = default; + +//---------------------------------------------------------------------------- +void ctkDICOMThumbnailGeneratorWorker::requestCancel() +{ + Q_D(ctkDICOMThumbnailGeneratorWorker); + d->wasCancelled = true; +} + +//---------------------------------------------------------------------------- +void ctkDICOMThumbnailGeneratorWorker::run() +{ + Q_D(const ctkDICOMThumbnailGeneratorWorker); + QSharedPointer thumbnailGeneratorJob = + qSharedPointerObjectCast(this->Job); + if (!thumbnailGeneratorJob) + { + return; + } + + QSharedPointer scheduler = + qSharedPointerObjectCast(this->Scheduler); + if (!scheduler) + { + this->onJobCanceled(d->wasCancelled); + return; + } + + if (d->wasCancelled) + { + this->onJobCanceled(d->wasCancelled); + return; + } + + thumbnailGeneratorJob->setStatus(ctkAbstractJob::JobStatus::Running); + + logger.debug(QString("ctkDICOMThumbnailGeneratorWorker : running job %1 in thread %2.\n") + .arg(thumbnailGeneratorJob->jobUID()) + .arg(QString::number(reinterpret_cast(QThread::currentThreadId())), 16)); + + ctkDICOMDatabase database; + QString dbConnectionName = + "db_" + QString::number(reinterpret_cast(QThread::currentThreadId()), 16); + database.openDatabase(thumbnailGeneratorJob->databaseFilename(), dbConnectionName); + QSharedPointer thumbnailGenerator = + QSharedPointer(new ctkDICOMThumbnailGenerator); + database.setThumbnailGenerator(thumbnailGenerator.data()); + database.storeThumbnailFile(thumbnailGeneratorJob->dicomFilePath(), + thumbnailGeneratorJob->studyInstanceUID(), + thumbnailGeneratorJob->seriesInstanceUID(), + thumbnailGeneratorJob->sopInstanceUID(), + thumbnailGeneratorJob->modality(), + thumbnailGeneratorJob->backgroundColor()); + database.closeDatabase(); + + if (d->wasCancelled) + { + this->onJobCanceled(d->wasCancelled); + return; + } + QSharedPointer jobResponseSet = + QSharedPointer(new ctkDICOMJobResponseSet); + + jobResponseSet->setJobType(ctkDICOMJobResponseSet::JobType::ThumbnailGenerator); + jobResponseSet->setPatientID(thumbnailGeneratorJob->patientID()); + jobResponseSet->setStudyInstanceUID(thumbnailGeneratorJob->studyInstanceUID()); + jobResponseSet->setSeriesInstanceUID(thumbnailGeneratorJob->seriesInstanceUID()); + jobResponseSet->setSOPInstanceUID(thumbnailGeneratorJob->sopInstanceUID()); + jobResponseSet->setJobUID(thumbnailGeneratorJob->jobUID()); + + thumbnailGeneratorJob->progressJobDetail(jobResponseSet->toVariant()); + thumbnailGeneratorJob->setStatus(ctkAbstractJob::JobStatus::Finished); +} + +//---------------------------------------------------------------------------- +void ctkDICOMThumbnailGeneratorWorker::setJob(QSharedPointer job) +{ + QSharedPointer ThumbnailGeneratorJob = + qSharedPointerObjectCast(job); + if (!ThumbnailGeneratorJob) + { + return; + } + + this->Superclass::setJob(job); +} diff --git a/Libs/DICOM/Core/ctkDICOMThumbnailGeneratorWorker.h b/Libs/DICOM/Core/ctkDICOMThumbnailGeneratorWorker.h new file mode 100644 index 0000000000..4b14314998 --- /dev/null +++ b/Libs/DICOM/Core/ctkDICOMThumbnailGeneratorWorker.h @@ -0,0 +1,75 @@ +/*========================================================================= + + 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 __ctkDICOMThumbnailGeneratorWorker_h +#define __ctkDICOMThumbnailGeneratorWorker_h + +// Qt includes +#include +#include + +// ctkDICOMCore includes +#include "ctkDICOMCoreExport.h" +#include "ctkAbstractWorker.h" +class ctkDICOMThumbnailGenerator; +class ctkDICOMThumbnailGeneratorWorkerPrivate; + +/// \ingroup DICOM_Core +class CTK_DICOM_CORE_EXPORT ctkDICOMThumbnailGeneratorWorker : public ctkAbstractWorker +{ + Q_OBJECT + +public: + typedef ctkAbstractWorker Superclass; + explicit ctkDICOMThumbnailGeneratorWorker(); + virtual ~ctkDICOMThumbnailGeneratorWorker(); + + /// Execute worker. This method is run by the QThreadPool and is thread safe + void run() override; + + /// Cancel worker. This method is thread safe + void requestCancel() override; + + ///@{ + /// Job. + /// These methods are not thread safe + void setJob(QSharedPointer job) override; + using ctkAbstractWorker::setJob; + ///@} + +protected: + QScopedPointer d_ptr; + + /// Constructor allowing derived class to specify a specialized pimpl. + /// + /// \note You are responsible to call init() in the constructor of + /// derived class. Doing so ensures the derived class is fully + /// instantiated in case virtual method are called within init() itself. + ctkDICOMThumbnailGeneratorWorker(ctkDICOMThumbnailGeneratorWorkerPrivate* pimpl); + +private: + Q_DECLARE_PRIVATE(ctkDICOMThumbnailGeneratorWorker); + Q_DISABLE_COPY(ctkDICOMThumbnailGeneratorWorker); +}; + +#endif diff --git a/Libs/DICOM/Core/ctkDICOMThumbnailGeneratorWorker_p.h b/Libs/DICOM/Core/ctkDICOMThumbnailGeneratorWorker_p.h new file mode 100644 index 0000000000..601e4636c3 --- /dev/null +++ b/Libs/DICOM/Core/ctkDICOMThumbnailGeneratorWorker_p.h @@ -0,0 +1,51 @@ +/*========================================================================= + + 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 __ctkDICOMThumbnailGeneratorWorkerPrivate_h +#define __ctkDICOMThumbnailGeneratorWorkerPrivate_h + +// Qt includes +#include +#include + +// ctkDICOMCore includes +#include "ctkDICOMThumbnailGeneratorWorker.h" + +//------------------------------------------------------------------------------ +class ctkDICOMThumbnailGeneratorWorkerPrivate : public QObject +{ + Q_OBJECT + Q_DECLARE_PUBLIC(ctkDICOMThumbnailGeneratorWorker) + +protected: + ctkDICOMThumbnailGeneratorWorker* const q_ptr; + +public: + ctkDICOMThumbnailGeneratorWorkerPrivate(ctkDICOMThumbnailGeneratorWorker* object); + virtual ~ctkDICOMThumbnailGeneratorWorkerPrivate(); + +public: + bool wasCancelled; +}; + +#endif diff --git a/Libs/DICOM/Widgets/CMakeLists.txt b/Libs/DICOM/Widgets/CMakeLists.txt index df50cfd71f..d5604cb055 100644 --- a/Libs/DICOM/Widgets/CMakeLists.txt +++ b/Libs/DICOM/Widgets/CMakeLists.txt @@ -48,8 +48,6 @@ set(KIT_SRCS ctkDICOMTableManager.cpp ctkDICOMTableView.cpp ctkDICOMTableView.h - ctkDICOMThumbnailGenerator.cpp - ctkDICOMThumbnailGenerator.h ctkDICOMThumbnailListWidget.cpp ctkDICOMThumbnailListWidget.h ctkDICOMVisualBrowserWidget.cpp @@ -76,7 +74,6 @@ set(KIT_MOC_SRCS ctkDICOMStudyItemWidget.h ctkDICOMTableManager.h ctkDICOMTableView.h - ctkDICOMThumbnailGenerator.h ctkDICOMThumbnailListWidget.h ctkDICOMVisualBrowserWidget.h ) diff --git a/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp index c16d3714b4..1e3b904eb2 100644 --- a/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp @@ -119,7 +119,11 @@ class QCenteredItemModel : public QStandardItemModel 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(); + QStringList clearJobsByType(QString type); + QStringList clearCompletedJobs(); + QStringList clearFailedJobs(); + QStringList clearUserStoppedJobs(); + void removeRowsByJobUIDs(QStringList jobUIDs); static Columns getColumnIndexFromString(QString columnString); static QString getColumnStringFromIndex(Columns columnIndex); }; @@ -186,6 +190,10 @@ QString QCenteredItemModel::getJobTypeAsString(QString jobClass, ctkDICOMJob::DI { return ctkDICOMJobListWidget::tr("Inserter"); } + else if (jobClass == "ctkDICOMThumbnailGeneratorJob") + { + return ctkDICOMJobListWidget::tr("Thumbnail generator"); + } return QString(); } @@ -199,8 +207,10 @@ void QCenteredItemModel::addJob(const ctkDICOMJobDetail &td, return; } - int row = 0; // add the job to the top + int row = 0; + QString backupLogging, creationDateTime; this->insertRow(row); + creationDateTime = td.CreationDateTime; QString jobType = this->getJobTypeAsString(td.JobClass, td.DICOMLevel); this->setData(this->index(row, Columns::JobType), jobType); @@ -223,8 +233,8 @@ void QCenteredItemModel::addJob(const ctkDICOMJobDetail &td, } 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::CreationDateTime), creationDateTime); + this->setData(this->index(row, Columns::CreationDateTime), creationDateTime, Qt::ToolTipRole); this->setData(this->index(row, Columns::StartDateTime), QString("-")); this->setData(this->index(row, Columns::CompletionDateTime), QString("-")); @@ -291,6 +301,10 @@ void QCenteredItemModel::updateJobStatus(const ctkDICOMJobDetail &td, const JobS { statusIcon = QIcon(":/Icons/pending.svg"); statusText = ctkDICOMJobListWidget::tr("queued"); + QList data; + data.append(0); + data.append(100); + this->setData(this->index(row, Columns::Progress), data); } else if (status == Running) { @@ -421,20 +435,61 @@ void QCenteredItemModel::setProgressBar(int row, const ctkDICOMJobDetail &td, ct } //---------------------------------------------------------------------------- -void QCenteredItemModel::clearCompletedJobs() +QStringList QCenteredItemModel::clearJobsByType(QString type) { #if (QT_VERSION >= QT_VERSION_CHECK(5,15,0)) - QList attemptFailedList = this->findItems(ctkDICOMJobListWidget::tr("attempt-failed"), Qt::MatchRegularExpression, Columns::Status); - QList completedList = this->findItems(ctkDICOMJobListWidget::tr("completed"), Qt::MatchRegularExpression, Columns::Status); + QList list = this->findItems(type, Qt::MatchRegularExpression, Columns::Status); #else - QList attemptFailedList = this->findItems(ctkDICOMJobListWidget::tr("attempt-failed"), Qt::MatchRegExp, Columns::Status); - QList completedList = this->findItems(ctkDICOMJobListWidget::tr("completed"), Qt::MatchRegExp, Columns::Status); + QList list = this->findItems(type, Qt::MatchRegExp, Columns::Status); #endif - completedList.append(attemptFailedList); - foreach (QStandardItem* item, completedList) + QStringList jobUIDs; + foreach (QStandardItem* item, list) + { + int row = item->row(); + jobUIDs.append(this->index(row, QCenteredItemModel::Columns::JobUID).data().toString()); + this->removeRow(row); + } + + return jobUIDs; +} + +//---------------------------------------------------------------------------- +QStringList QCenteredItemModel::clearCompletedJobs() +{ + QStringList jobUIDs; + jobUIDs.append(this->clearJobsByType(ctkDICOMJobListWidget::tr("attempt-failed"))); + jobUIDs.append(this->clearJobsByType(ctkDICOMJobListWidget::tr("completed"))); + return jobUIDs; +} + +//---------------------------------------------------------------------------- +QStringList QCenteredItemModel::clearFailedJobs() +{ + return this->clearJobsByType(ctkDICOMJobListWidget::tr("failed"));; +} + +//---------------------------------------------------------------------------- +QStringList QCenteredItemModel::clearUserStoppedJobs() +{ + return this->clearJobsByType(ctkDICOMJobListWidget::tr("user-stopped"));; +} + +//---------------------------------------------------------------------------- +void QCenteredItemModel::removeRowsByJobUIDs(QStringList jobUIDs) +{ + foreach (QString jobUID, jobUIDs) { - this->removeRow(item->row()); +#if (QT_VERSION >= QT_VERSION_CHECK(5,15,0)) + QList list = this->findItems(jobUID, Qt::MatchRegularExpression, Columns::JobUID); +#else + QList list = this->findItems(jobUID, Qt::MatchRegExp, Columns::JobUID); +#endif + if (!list.empty()) + { + int row = list.first()->row(); + this->removeRow(row); + } } } @@ -489,7 +544,7 @@ QCenteredItemModel::Columns QCenteredItemModel::getColumnIndexFromString(QString { return Columns::SeriesInstanceUID; } - else if (columnString == ctkDICOMJobListWidget::tr("SOP UID")) + else if (columnString == ctkDICOMJobListWidget::tr("SOPInstance UID")) { return Columns::SOPInstanceUID; } @@ -920,7 +975,7 @@ void ctkDICOMJobListWidgetPrivate::updateJobsDetailsWidget() //---------------------------------------------------------------------------- void ctkDICOMJobListWidgetPrivate::retryJobs() { - QMap jobsUIDsToRetry; + QStringList jobsUIDsToRetry; QItemSelectionModel *select = this->JobsView->selectionModel(); QModelIndexList selectedRows = select->selectedRows(); foreach (QModelIndex rowIndex, selectedRows) @@ -930,8 +985,6 @@ void ctkDICOMJobListWidgetPrivate::retryJobs() (row, QCenteredItemModel::Columns::Status).data().toString(); QString jobClass = this->showCompletedProxyModel->index (row, QCenteredItemModel::Columns::JobClass).data().toString(); - QString jobUID = this->showCompletedProxyModel->index - (row, QCenteredItemModel::Columns::JobUID).data().toString(); if (status != ctkDICOMJobListWidget::tr("failed") && status != ctkDICOMJobListWidget::tr("user-stopped")) { @@ -943,85 +996,12 @@ void ctkDICOMJobListWidgetPrivate::retryJobs() continue; } - ctkDICOMJobDetail jobDetail; - jobDetail.JobClass = this->showCompletedProxyModel->index - (row, QCenteredItemModel::Columns::JobClass).data().toString(); - - QString DICOMLevelString = this->showCompletedProxyModel->index - (row, QCenteredItemModel::Columns::DICOMLevel).data().toString(); - ctkDICOMJob::DICOMLevels DICOMLevel = ctkDICOMJob::DICOMLevels::None; - if (DICOMLevelString == "Patients") - { - DICOMLevel = ctkDICOMJob::DICOMLevels::Patients; - } - else 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 = this->showCompletedProxyModel->index - (row, QCenteredItemModel::Columns::PatientID).data().toString(); - jobDetail.StudyInstanceUID = this->showCompletedProxyModel->index - (row, QCenteredItemModel::Columns::StudyInstanceUID).data().toString(); - jobDetail.SeriesInstanceUID = this->showCompletedProxyModel->index - (row, QCenteredItemModel::Columns::SeriesInstanceUID).data().toString(); - jobDetail.SOPInstanceUID = this->showCompletedProxyModel->index - (row, QCenteredItemModel::Columns::SOPInstanceUID).data().toString(); - jobDetail.ConnectionName = this->showCompletedProxyModel->index - (row, QCenteredItemModel::Columns::Connection).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) - QMap filteredJobsUIDsToRetry; - foreach (QString jobUID, jobsUIDsToRetry.keys()) - { - ctkDICOMJobDetail td = jobsUIDsToRetry.value(jobUID); - bool duplicate = false; - foreach (QString filteredJobUID, filteredJobsUIDsToRetry.keys()) - { - if (filteredJobUID == jobUID) - { - continue; - } - - ctkDICOMJobDetail filteredTD = jobsUIDsToRetry.value(filteredJobUID); - if (td.JobClass == filteredTD.JobClass && - td.DICOMLevel == filteredTD.DICOMLevel && - td.PatientID == filteredTD.PatientID && - td.StudyInstanceUID == filteredTD.StudyInstanceUID && - td.SeriesInstanceUID == filteredTD.SeriesInstanceUID && - td.SOPInstanceUID == filteredTD.SOPInstanceUID && - td.ConnectionName == filteredTD.ConnectionName) - { - duplicate = true; - break; - } - } - - if (duplicate) - { - continue; - } - else - { - filteredJobsUIDsToRetry.insert(jobUID, td); - } + jobsUIDsToRetry.append(this->showCompletedProxyModel->index + (row, QCenteredItemModel::Columns::JobUID).data().toString()); } - this->Scheduler->runJobs(filteredJobsUIDsToRetry); this->JobsView->clearSelection(); + this->Scheduler->retryJobs(jobsUIDsToRetry); } //---------------------------------------------------------------------------- @@ -1092,7 +1072,15 @@ void ctkDICOMJobListWidget::onJobInitialized(QVariant data) return; } - d->dataModel->addJob(td, d->Scheduler->dicomDatabase()); + QList list = d->dataModel->findItems(td.JobUID, Qt::MatchExactly, QCenteredItemModel::Columns::JobUID); + if (list.empty()) + { + d->dataModel->addJob(td, d->Scheduler->dicomDatabase()); + } + else + { + d->dataModel->updateJobStatus(td, QCenteredItemModel::Initialized); + } } //---------------------------------------------------------------------------- @@ -1241,7 +1229,7 @@ void ctkDICOMJobListWidget::onJobsViewSelectionChanged() QString jobClass = d->showCompletedProxyModel->index (row, QCenteredItemModel::Columns::JobClass).data().toString(); - if ((status == tr("failed") || status == tr("user-stopped")) && + if ((status == ctkDICOMJobListWidget::tr("failed") || status == ctkDICOMJobListWidget::tr("user-stopped")) && (jobClass == "ctkDICOMQueryJob" || jobClass == "ctkDICOMRetrieveJob")) { failedJobSelected = true; @@ -1261,7 +1249,9 @@ void ctkDICOMJobListWidget::onJobsViewSelectionChanged() QString jobClass = d->showCompletedProxyModel->index (row, QCenteredItemModel::Columns::JobClass).data().toString(); - if ((status == tr("in-progress") || status == tr("queued") || status == tr("initialized")) && + if ((status == ctkDICOMJobListWidget::tr("in-progress") || + status == ctkDICOMJobListWidget::tr("queued") || + status == ctkDICOMJobListWidget::tr("initialized")) && jobClass != "ctkDICOMStorageListenerJob") { inProgressJobSelected = true; @@ -1294,15 +1284,15 @@ void ctkDICOMJobListWidget::onStopButtonClicked() (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") || status == tr("initialized")) + if (status == ctkDICOMJobListWidget::tr("in-progress") || + status == ctkDICOMJobListWidget::tr("queued") || + status == ctkDICOMJobListWidget::tr("initialized")) { jobsUIDsToStop.append(jobUID); } } - QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); d->Scheduler->stopJobsByJobUIDs(jobsUIDsToStop); - QApplication::restoreOverrideCursor(); d->JobsView->clearSelection(); } @@ -1343,7 +1333,11 @@ void ctkDICOMJobListWidget::onShowCompletedButtonToggled(bool toggled) void ctkDICOMJobListWidget::onClearCompletedButtonClicked() { Q_D(ctkDICOMJobListWidget); - d->dataModel->clearCompletedJobs(); + QStringList jobUIDs = d->dataModel->clearCompletedJobs(); + // Delete jobs pointer of completed jobs + // (for these the scheduler has only cleaned the memory footprint). + // NOTE: attempt-failed are considered completed jobs + d->Scheduler->deleteJobs(jobUIDs); } //---------------------------------------------------------------------------- @@ -1351,7 +1345,19 @@ void ctkDICOMJobListWidget::onClearAllButtonClicked() { Q_D(ctkDICOMJobListWidget); QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); - d->Scheduler->stopAllJobs(); + // Stop all initialized, queued, in-progress jobs and clean the UI rows. + // Jobs will destroyed automatically by the scheduler. + QStringList stoppedJobUIDs = d->Scheduler->stopAllJobs(); + d->dataModel->removeRowsByJobUIDs(stoppedJobUIDs); + + // Clean UI rows of completed, failed, user-stopped jobs + QStringList jobUIDsToDelete = d->dataModel->clearCompletedJobs(); + jobUIDsToDelete.append(d->dataModel->clearFailedJobs()); + jobUIDsToDelete.append(d->dataModel->clearUserStoppedJobs()); + // Delete jobs pointer of completed/failed/user-stopped jobs + // (for these the scheduler has only cleaned the memory footprint). + // NOTE: attempt-failed are considered completed jobs + d->Scheduler->deleteJobs(jobUIDsToDelete); + QApplication::restoreOverrideCursor(); - d->dataModel->removeRows(0, d->dataModel->rowCount()); } diff --git a/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.cpp index 9f7b58781d..680f7590ad 100644 --- a/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.cpp @@ -65,8 +65,6 @@ class ctkDICOMPatientItemWidgetPrivate : public Ui_ctkDICOMPatientItemWidget ~ctkDICOMPatientItemWidgetPrivate(); void init(QWidget* parentWidget); - void connectScheduler(); - void disconnectScheduler(); QString getPatientItemFromPatientID(const QString& patientID); QString formatDate(const QString&); bool isStudyItemAlreadyAdded(const QString& studyItem); @@ -102,6 +100,7 @@ class ctkDICOMPatientItemWidgetPrivate : public Ui_ctkDICOMPatientItemWidget QStringList AllowedServers; ctkDICOMPatientItemWidget::OperationStatus Status; + QString StoppedJobUID; bool IsGUIUpdating; bool QueryOn; @@ -135,6 +134,7 @@ ctkDICOMPatientItemWidgetPrivate::ctkDICOMPatientItemWidgetPrivate(ctkDICOMPatie this->AllowedServers = QStringList(); this->Status = ctkDICOMPatientItemWidget::NoOperation; + this->StoppedJobUID = ""; this->IsGUIUpdating = false; this->QueryOn = true; @@ -165,48 +165,6 @@ void ctkDICOMPatientItemWidgetPrivate::init(QWidget* parentWidget) q, SLOT(onPatientServersCheckableComboBoxChanged())); } -//---------------------------------------------------------------------------- -void ctkDICOMPatientItemWidgetPrivate::connectScheduler() -{ - Q_Q(ctkDICOMPatientItemWidget); - if (!this->Scheduler) - { - return; - } - - QObject::connect(this->Scheduler.data(), SIGNAL(progressJobDetail(QList)), - q, SLOT(updateGUIFromScheduler(QList))); - QObject::connect(this->Scheduler.data(), SIGNAL(jobStarted(QList)), - q, SLOT(onJobStarted(QList))); - QObject::connect(this->Scheduler.data(), SIGNAL(jobUserStopped(QList)), - q, SLOT(onJobUserStopped(QList))); - QObject::connect(this->Scheduler.data(), SIGNAL(jobFailed(QList)), - q, SLOT(onJobFailed(QList))); - QObject::connect(this->Scheduler.data(), SIGNAL(jobFinished(QList)), - q, SLOT(onJobFinished(QList))); -} - -//---------------------------------------------------------------------------- -void ctkDICOMPatientItemWidgetPrivate::disconnectScheduler() -{ - Q_Q(ctkDICOMPatientItemWidget); - if (!this->Scheduler) - { - return; - } - - QObject::disconnect(this->Scheduler.data(), SIGNAL(progressJobDetail(QList)), - q, SLOT(updateGUIFromScheduler(QList))); - QObject::disconnect(this->Scheduler.data(), SIGNAL(jobStarted(QList)), - q, SLOT(onJobStarted(QList))); - QObject::disconnect(this->Scheduler.data(), SIGNAL(jobUserStopped(QList)), - q, SLOT(onJobUserStopped(QList))); - QObject::disconnect(this->Scheduler.data(), SIGNAL(jobFailed(QList)), - q, SLOT(onJobFailed(QList))); - QObject::disconnect(this->Scheduler.data(), SIGNAL(jobFinished(QList)), - q, SLOT(onJobFinished(QList))); -} - //---------------------------------------------------------------------------- QString ctkDICOMPatientItemWidgetPrivate::getPatientItemFromPatientID(const QString& patientID) { @@ -650,6 +608,7 @@ CTK_SET_CPP(ctkDICOMPatientItemWidget, int, setNumberOfOpenedStudiesPerPatient, CTK_GET_CPP(ctkDICOMPatientItemWidget, int, numberOfOpenedStudiesPerPatient, NumberOfOpenedStudiesPerPatient); CTK_SET_CPP(ctkDICOMPatientItemWidget, const ctkDICOMStudyItemWidget::ThumbnailSizeOption&, setThumbnailSize, ThumbnailSize); CTK_GET_CPP(ctkDICOMPatientItemWidget, ctkDICOMStudyItemWidget::ThumbnailSizeOption, thumbnailSize, ThumbnailSize); +CTK_GET_CPP(ctkDICOMPatientItemWidget, QString, stoppedJobUID, StoppedJobUID); //------------------------------------------------------------------------------ void ctkDICOMPatientItemWidget::setPatientItem(const QString& patientItem) @@ -677,18 +636,14 @@ QSharedPointer ctkDICOMPatientItemWidget::schedulerShared() c void ctkDICOMPatientItemWidget::setScheduler(ctkDICOMScheduler& scheduler) { Q_D(ctkDICOMPatientItemWidget); - d->disconnectScheduler(); d->Scheduler = QSharedPointer(&scheduler, skipDelete); - d->connectScheduler(); } //---------------------------------------------------------------------------- void ctkDICOMPatientItemWidget::setScheduler(QSharedPointer scheduler) { Q_D(ctkDICOMPatientItemWidget); - d->disconnectScheduler(); d->Scheduler = scheduler; - d->connectScheduler(); } //---------------------------------------------------------------------------- @@ -773,15 +728,15 @@ void ctkDICOMPatientItemWidget::addStudyItemWidget(const QString& studyItem) QString studyDescription = d->DicomDatabase->fieldForStudy("StudyDescription", studyItem); if (studyDescription.isEmpty()) { - studyDescription = tr("UNDEFINED"); + studyDescription = ctkDICOMPatientItemWidget::tr("UNDEFINED"); } ctkDICOMStudyItemWidget* studyItemWidget = - new ctkDICOMStudyItemWidget(this, d->VisualDICOMBrowser.data()); + new ctkDICOMStudyItemWidget(d->VisualDICOMBrowser.data()); studyItemWidget->setStudyItem(studyItem); studyItemWidget->setPatientID(d->PatientID); studyItemWidget->setStudyInstanceUID(studyInstanceUID); - QString fullDescription = tr("Study"); + QString fullDescription = ctkDICOMPatientItemWidget::tr("Study"); if (!studyID.isEmpty()) { fullDescription += QString("Study ID %1").arg(studyID); @@ -831,9 +786,9 @@ void ctkDICOMPatientItemWidget::removeStudyItemWidget(const QString& studyItem) } if (d->StudyItemWidgetsConnectionMap.contains(studyItem)) - { + { this->disconnect(d->StudyItemWidgetsConnectionMap[studyItem]); - } + } this->disconnect(studyItemWidget->seriesListTableWidget(), SIGNAL(itemDoubleClicked(QTableWidgetItem *)), d->VisualDICOMBrowser.data(), SLOT(onLoad())); this->disconnect(studyItemWidget, SIGNAL(customContextMenuRequested(const QPoint&)), @@ -866,6 +821,26 @@ ctkDICOMStudyItemWidget* ctkDICOMPatientItemWidget::studyItemWidgetByStudyItem(c return nullptr; } +//------------------------------------------------------------------------------ +ctkDICOMStudyItemWidget *ctkDICOMPatientItemWidget::studyItemWidgetByStudyInstanceUID(const QString &StudyInstanceUID) +{ + Q_D(ctkDICOMPatientItemWidget); + + for (int studyIndex = 0; studyIndex < d->StudyItemWidgetsList.size(); ++studyIndex) + { + ctkDICOMStudyItemWidget* studyItemWidget = + qobject_cast(d->StudyItemWidgetsList[studyIndex]); + if (!studyItemWidget || studyItemWidget->studyInstanceUID() != StudyInstanceUID) + { + continue; + } + + return studyItemWidget; + } + + return nullptr; +} + //------------------------------------------------------------------------------ void ctkDICOMPatientItemWidget::setSelection(bool selected) { @@ -903,7 +878,6 @@ void ctkDICOMPatientItemWidget::generateStudies(bool query, bool retrieve) //------------------------------------------------------------------------------ void ctkDICOMPatientItemWidget::generateSeriesAtToggle(bool toggled, const QString& studyItem) { - Q_D(ctkDICOMPatientItemWidget); if (!toggled || studyItem.isEmpty()) { return; @@ -919,141 +893,189 @@ void ctkDICOMPatientItemWidget::generateSeriesAtToggle(bool toggled, const QStri } //------------------------------------------------------------------------------ -void ctkDICOMPatientItemWidget::updateGUIFromScheduler(QList datas) +void ctkDICOMPatientItemWidget::updateGUIFromScheduler(QVariant data) { Q_D(ctkDICOMPatientItemWidget); - bool updateStudies = false; - foreach (QVariant data, datas) + ctkDICOMJobDetail td = data.value(); + if (td.JobUID.isEmpty()) { - ctkDICOMJobDetail td = data.value(); - if (td.JobUID.isEmpty()) - { - d->createStudies(); - continue; - } - - if (td.PatientID != d->PatientID) - { - continue; - } + d->createStudies(); + return; + } - emit this->progressJobDetail(data); + if (td.PatientID != d->PatientID) + { + return; + } - if (td.JobType != ctkDICOMJobResponseSet::JobType::QueryStudies) + if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryStudies) + { + d->createStudies(); + } + else if (td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries || + td.JobType == ctkDICOMJobResponseSet::JobType::QueryInstances || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance || + td.JobType == ctkDICOMJobResponseSet::JobType::StoreSOPInstance || + td.JobType == ctkDICOMJobResponseSet::JobType::ThumbnailGenerator || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries) + { + ctkDICOMStudyItemWidget* studyItemWidget = this->studyItemWidgetByStudyInstanceUID(td.StudyInstanceUID); + if (studyItemWidget) { - continue; + studyItemWidget->updateGUIFromScheduler(data); } + } +} + +//------------------------------------------------------------------------------ +void ctkDICOMPatientItemWidget::onJobStarted(QVariant data) +{ + Q_D(ctkDICOMPatientItemWidget); + ctkDICOMJobDetail td = data.value(); - updateStudies = true; + if (td.JobUID.isEmpty() || + td.PatientID != d->PatientID) + { + return; } - if (updateStudies) + if (td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries || + td.JobType == ctkDICOMJobResponseSet::JobType::QueryInstances || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries) { - d->createStudies(); + ctkDICOMStudyItemWidget* studyItemWidget = this->studyItemWidgetByStudyInstanceUID(td.StudyInstanceUID); + if (studyItemWidget) + { + studyItemWidget->onJobStarted(data); + } } } //------------------------------------------------------------------------------ -void ctkDICOMPatientItemWidget::onJobStarted(QList datas) +void ctkDICOMPatientItemWidget::onJobUserStopped(QVariant data) { Q_D(ctkDICOMPatientItemWidget); - foreach (QVariant data, datas) + + ctkDICOMJobDetail td = data.value(); + + if (td.JobUID.isEmpty() || + td.PatientID != d->PatientID) { - ctkDICOMJobDetail td = data.value(); + return; + } - if (td.JobUID.isEmpty() || - td.PatientID != d->PatientID) + if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryStudies) + { + d->StoppedJobUID = td.JobUID; + } + else if (td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries || + td.JobType == ctkDICOMJobResponseSet::JobType::QueryInstances || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries) + { + ctkDICOMStudyItemWidget* studyItemWidget = this->studyItemWidgetByStudyInstanceUID(td.StudyInstanceUID); + if (studyItemWidget) { - continue; + studyItemWidget->onJobUserStopped(data); } - - emit this->jobStarted(data); } } //------------------------------------------------------------------------------ -void ctkDICOMPatientItemWidget::onJobUserStopped(QList datas) +void ctkDICOMPatientItemWidget::onJobFailed(QVariant data) { Q_D(ctkDICOMPatientItemWidget); - foreach (QVariant data, datas) + + ctkDICOMJobDetail td = data.value(); + + if (td.JobUID.isEmpty() || + td.PatientID != d->PatientID) { - ctkDICOMJobDetail td = data.value(); + return; + } - if (td.JobUID.isEmpty() || - td.PatientID != d->PatientID) + if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryStudies) + { + d->StoppedJobUID = td.JobUID; + } + else if (td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries || + td.JobType == ctkDICOMJobResponseSet::JobType::QueryInstances || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries) + { + ctkDICOMStudyItemWidget* studyItemWidget = this->studyItemWidgetByStudyInstanceUID(td.StudyInstanceUID); + if (studyItemWidget) { - continue; + studyItemWidget->onJobFailed(data); } - - emit this->jobUserStopped(data); } } //------------------------------------------------------------------------------ -void ctkDICOMPatientItemWidget::onJobFailed(QList datas) +void ctkDICOMPatientItemWidget::onJobFinished(QVariant data) { Q_D(ctkDICOMPatientItemWidget); - foreach (QVariant data, datas) + + ctkDICOMJobDetail td = data.value(); + if (td.JobUID.isEmpty() || + td.PatientID != d->PatientID) { - ctkDICOMJobDetail td = data.value(); + return; + } - if (td.JobUID.isEmpty() || - td.PatientID != d->PatientID) + if (td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance) + { + ctkDICOMStudyItemWidget* studyItemWidget = this->studyItemWidgetByStudyInstanceUID(td.StudyInstanceUID); + if (studyItemWidget) { - continue; + studyItemWidget->onJobFinished(data); } - - emit this->jobFailed(data); } } //------------------------------------------------------------------------------ -void ctkDICOMPatientItemWidget::onJobFinished(QList datas) +void ctkDICOMPatientItemWidget::onInserterJobFinished(QVariant data) { Q_D(ctkDICOMPatientItemWidget); - foreach (QVariant data, datas) + + ctkDICOMJobDetail td = data.value(); + if (td.JobUID.isEmpty() || + td.JobType != ctkDICOMJobResponseSet::JobType::Inserter) { - ctkDICOMJobDetail td = data.value(); - if (td.JobType == ctkDICOMJobResponseSet::JobType::Inserter) + return; + } + + foreach (ctkDICOMStudyItemWidget* studyItemWidget, d->StudyItemWidgetsList) + { + if (!studyItemWidget) { - foreach (ctkDICOMStudyItemWidget* studyItemWidget, d->StudyItemWidgetsList) + continue; + } + + QTableWidget* seriesListTableWidget = studyItemWidget->seriesListTableWidget(); + for (int row = 0; row < seriesListTableWidget->rowCount(); row++) + { + for (int column = 0; column < seriesListTableWidget->columnCount(); column++) { - if (!studyItemWidget) + ctkDICOMSeriesItemWidget* seriesItemWidget = + qobject_cast(seriesListTableWidget->cellWidget(row, column)); + if (!seriesItemWidget) { continue; } - - QTableWidget* seriesListTableWidget = studyItemWidget->seriesListTableWidget(); - for (int row = 0; row < seriesListTableWidget->rowCount(); row++) + if (seriesItemWidget->referenceSeriesInserterJobUID() == td.JobUID || + seriesItemWidget->referenceInstanceInserterJobUID() == td.JobUID || + seriesItemWidget->referenceSeriesInserterJobUID() == "StorageListener" || + seriesItemWidget->referenceInstanceInserterJobUID() == "StorageListener") { - for (int column = 0; column < seriesListTableWidget->columnCount(); column++) - { - ctkDICOMSeriesItemWidget* seriesItemWidget = - qobject_cast(seriesListTableWidget->cellWidget(row, column)); - if (!seriesItemWidget) - { - continue; - } - if (seriesItemWidget->referenceSeriesInserterJobUID() == td.JobUID || - seriesItemWidget->referenceInstanceInserterJobUID() == td.JobUID || - seriesItemWidget->referenceSeriesInserterJobUID() == "StorageListener" || - seriesItemWidget->referenceInstanceInserterJobUID() == "StorageListener") - { - seriesItemWidget->onJobFinished(data); - } - } + seriesItemWidget->onJobFinished(data); } } } - - if (td.JobUID.isEmpty() || - td.PatientID != d->PatientID) - { - continue; - } - - emit this->jobFinished(data); } } diff --git a/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.h b/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.h index 7245240920..360ddecdbc 100644 --- a/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.h +++ b/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.h @@ -51,6 +51,7 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMPatientItemWidget : public QWidget Q_PROPERTY(ctkDICOMStudyItemWidget::ThumbnailSizeOption thumbnailSize READ thumbnailSize WRITE setThumbnailSize); Q_PROPERTY(QStringList allowedServers READ allowedServers WRITE setAllowedServers); Q_PROPERTY(OperationStatus operationStatus READ operationStatus WRITE setOperationStatus); + Q_PROPERTY(QString stoppedJobUID READ stoppedJobUID); public: typedef QWidget Superclass; @@ -177,6 +178,7 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMPatientItemWidget : public QWidget Q_INVOKABLE void addStudyItemWidget(const QString& studyItem); Q_INVOKABLE void removeStudyItemWidget(const QString& studyItem); Q_INVOKABLE ctkDICOMStudyItemWidget* studyItemWidgetByStudyItem(const QString& studyItem); + Q_INVOKABLE ctkDICOMStudyItemWidget* studyItemWidgetByStudyInstanceUID(const QString& studyInstanceUID); ///@} /// Set selection for all studies/series @@ -205,14 +207,18 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMPatientItemWidget : public QWidget OperationStatus operationStatus() const; ///@} + /// Last stopped job information operated by this widget + Q_INVOKABLE QString stoppedJobUID() const; + public Q_SLOTS: void generateStudies(bool query = true, bool retrieve = true); void generateSeriesAtToggle(bool toggled = true, const QString& studyItem = ""); - void updateGUIFromScheduler(QList); - void onJobStarted(QList); - void onJobUserStopped(QList); - void onJobFailed(QList); - void onJobFinished(QList); + void updateGUIFromScheduler(QVariant); + void onJobStarted(QVariant); + void onJobUserStopped(QVariant); + void onJobFailed(QVariant); + void onJobFinished(QVariant); + void onInserterJobFinished(QVariant); void onSeriesItemClicked(); void raiseSelectedSeriesJobsPriority(); void onPatientServersCheckableComboBoxChanged(); @@ -220,12 +226,6 @@ public Q_SLOTS: Q_SIGNALS: /// Emitted when the GUI finished to update after a studies query. void updateGUIFinished(); - /// Propagate jobs signals to the tree - void jobStarted(QVariant); - void jobUserStopped(QVariant); - void jobFinished(QVariant); - void jobFailed(QVariant); - void progressJobDetail(QVariant); protected: QScopedPointer d_ptr; diff --git a/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp index ec3621de7f..2c78bc3acb 100644 --- a/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp @@ -68,13 +68,12 @@ class ctkDICOMSeriesItemWidgetPrivate : public Ui_ctkDICOMSeriesItemWidget ctkDICOMSeriesItemWidgetPrivate(ctkDICOMSeriesItemWidget& obj); ~ctkDICOMSeriesItemWidgetPrivate(); - void init(QWidget* top); - void connectToTop(); - void disconnectFromTop(); + void init(); QString getDICOMCenterFrameFromInstances(QStringList instancesList); void createThumbnail(ctkDICOMJobDetail td); QImage drawModalityThumbnail(); void drawThumbnail(const QString& dicomFilePath, + const QString& patientID, const QString& studyInstanceUID, const QString& seriesInstanceUID, const QString& sopInstanceUID, @@ -94,8 +93,6 @@ class ctkDICOMSeriesItemWidgetPrivate : public Ui_ctkDICOMSeriesItemWidget QSharedPointer DicomDatabase; QSharedPointer Scheduler; - QSharedPointer StudyWidget; - QMap Connections; QStringList AllowedServers; QString PatientID; @@ -107,6 +104,9 @@ class ctkDICOMSeriesItemWidgetPrivate : public Ui_ctkDICOMSeriesItemWidget QString Modality; QString ReferenceSeriesInserterJobUID; QString ReferenceInstanceInserterJobUID; + + QString StoppedJobUID; + bool StopJobs; bool RaiseJobsPriority; bool IsCloud; @@ -139,6 +139,8 @@ ctkDICOMSeriesItemWidgetPrivate::ctkDICOMSeriesItemWidgetPrivate(ctkDICOMSeriesI this->ReferenceSeriesInserterJobUID = ""; this->ReferenceInstanceInserterJobUID = ""; + this->StoppedJobUID = ""; + this->IsCloud = false; this->RetrieveFailed = false; this->RetrieveSeries = false; @@ -161,18 +163,14 @@ ctkDICOMSeriesItemWidgetPrivate::ctkDICOMSeriesItemWidgetPrivate(ctkDICOMSeriesI //---------------------------------------------------------------------------- ctkDICOMSeriesItemWidgetPrivate::~ctkDICOMSeriesItemWidgetPrivate() { - this->disconnectFromTop(); } //---------------------------------------------------------------------------- -void ctkDICOMSeriesItemWidgetPrivate::init(QWidget* top) +void ctkDICOMSeriesItemWidgetPrivate::init() { Q_Q(ctkDICOMSeriesItemWidget); this->setupUi(q); - this->StudyWidget = QSharedPointer(top, skipDelete); - this->connectToTop(); - this->SeriesThumbnail->setTransformationMode(Qt::TransformationMode::SmoothTransformation); this->SeriesThumbnail->textPushButton()->setElideMode(Qt::ElideRight); this->SeriesThumbnail->setSelectedColor(QColor::Invalid); @@ -181,56 +179,6 @@ void ctkDICOMSeriesItemWidgetPrivate::init(QWidget* top) q, SLOT(onOperationStatusButtonClicked(bool))); } -//---------------------------------------------------------------------------- -void ctkDICOMSeriesItemWidgetPrivate::connectToTop() -{ - Q_Q(ctkDICOMSeriesItemWidget); - if (!this->StudyWidget) - { - return; - } - - QMetaObject::Connection progressConnection = - QObject::connect(this->StudyWidget.data(), SIGNAL(progressJobDetail(QVariant)), - q, SLOT(updateGUIFromScheduler(QVariant))); - QMetaObject::Connection progressBarConnection = - QObject::connect(this->StudyWidget.data(), SIGNAL(progressJobDetail(QVariant)), - q, SLOT(updateSeriesProgressBar(QVariant))); - QMetaObject::Connection startedConnection = - QObject::connect(this->StudyWidget.data(), SIGNAL(jobStarted(QVariant)), - q, SLOT(onJobStarted(QVariant))); - QMetaObject::Connection userStoppedConnection = - QObject::connect(this->StudyWidget.data(), SIGNAL(jobUserStopped(QVariant)), - q, SLOT(onJobUserStopped(QVariant))); - QMetaObject::Connection failedConnection = - QObject::connect(this->StudyWidget.data(), SIGNAL(jobFailed(QVariant)), - q, SLOT(onJobFailed(QVariant))); - QMetaObject::Connection finishedConnection = - QObject::connect(this->StudyWidget.data(), SIGNAL(jobFinished(QVariant)), - q, SLOT(onJobFinished(QVariant))); - - this->Connections = - { - {"progress", progressConnection}, - {"progressBar", progressBarConnection}, - {"started", startedConnection}, - {"userStopped", userStoppedConnection}, - {"finished", finishedConnection}, - {"failed", failedConnection} - }; -} - -//---------------------------------------------------------------------------- -void ctkDICOMSeriesItemWidgetPrivate::disconnectFromTop() -{ - QObject::disconnect(this->Connections.value("progress")); - QObject::disconnect(this->Connections.value("progressBar")); - QObject::disconnect(this->Connections.value("started")); - QObject::disconnect(this->Connections.value("userStopped")); - QObject::disconnect(this->Connections.value("finished")); - QObject::disconnect(this->Connections.value("failed")); -} - //---------------------------------------------------------------------------- QString ctkDICOMSeriesItemWidgetPrivate::getDICOMCenterFrameFromInstances(QStringList instancesList) { @@ -409,11 +357,13 @@ void ctkDICOMSeriesItemWidgetPrivate::createThumbnail(ctkDICOMJobDetail td) if (jobSopInstanceUID.isEmpty() || ((jobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance || - jobType == ctkDICOMJobResponseSet::JobType::StoreSOPInstance) && + jobType == ctkDICOMJobResponseSet::JobType::StoreSOPInstance || + jobType == ctkDICOMJobResponseSet::JobType::ThumbnailGenerator) && jobSopInstanceUID == this->CentralFrameSOPInstanceUID) || renderThumbnail) { this->drawThumbnail(this->DicomDatabase->fileForInstance(this->CentralFrameSOPInstanceUID), + this->PatientID, this->StudyInstanceUID, this->SeriesInstanceUID, this->CentralFrameSOPInstanceUID, @@ -465,6 +415,7 @@ QImage ctkDICOMSeriesItemWidgetPrivate::drawModalityThumbnail() //---------------------------------------------------------------------------- void ctkDICOMSeriesItemWidgetPrivate::drawThumbnail(const QString& dicomFilePath, + const QString& patientID, const QString& studyInstanceUID, const QString& seriesInstanceUID, const QString& sopInstanceUID, @@ -485,6 +436,12 @@ void ctkDICOMSeriesItemWidgetPrivate::drawThumbnail(const QString& dicomFilePath return; } + if (patientID.isEmpty()) + { + logger.debug("drawThumbnail failed, patientID is empty. \n"); + return; + } + if (studyInstanceUID.isEmpty()) { logger.debug("drawThumbnail failed, studyInstanceUID is empty. \n"); @@ -510,7 +467,6 @@ void ctkDICOMSeriesItemWidgetPrivate::drawThumbnail(const QString& dicomFilePath } qreal scalingFactor = q->devicePixelRatioF(); - int scaledThumbnailSizePixel = this->ThumbnailSizePixel * scalingFactor; int margin = floor(this->ThumbnailSizePixel / 50.); int iconSize = floor(this->ThumbnailSizePixel / 6.); int textSize = floor(this->ThumbnailSizePixel / 12.); @@ -520,18 +476,14 @@ void ctkDICOMSeriesItemWidgetPrivate::drawThumbnail(const QString& dicomFilePath if (this->ThumbnailImage.isNull()) { - ctkDICOMThumbnailGenerator thumbnailGenerator; - thumbnailGenerator.setWidth(scaledThumbnailSizePixel); - thumbnailGenerator.setHeight(scaledThumbnailSizePixel); - QString thumbnailPath = this->DicomDatabase->thumbnailPathForInstance(studyInstanceUID, seriesInstanceUID, sopInstanceUID); if (thumbnailPath.isEmpty()) { QColor backgroundColor = this->SeriesThumbnail->palette().color(QPalette::Normal, QPalette::Window); - QVector colorVector = {backgroundColor.red(), backgroundColor.green(), backgroundColor.blue()}; - this->DicomDatabase->storeThumbnailFile(dicomFilePath, studyInstanceUID, seriesInstanceUID, - sopInstanceUID, modality, colorVector); - thumbnailPath = this->DicomDatabase->thumbnailPathForInstance(studyInstanceUID, seriesInstanceUID, sopInstanceUID); + this->Scheduler->generateThumbnail(dicomFilePath, patientID, studyInstanceUID, seriesInstanceUID, + sopInstanceUID, modality, backgroundColor, + this->RaiseJobsPriority ? QThread::HighestPriority : QThread::HighPriority); + return; } if (!this->ThumbnailImage.load(thumbnailPath)) @@ -694,6 +646,7 @@ void ctkDICOMSeriesItemWidgetPrivate::updateThumbnailProgressBar() // change icons QString file = this->DicomDatabase->fileForInstance(this->CentralFrameSOPInstanceUID); this->drawThumbnail(file, + this->PatientID, this->StudyInstanceUID, this->SeriesInstanceUID, this->CentralFrameSOPInstanceUID, @@ -713,6 +666,7 @@ void ctkDICOMSeriesItemWidgetPrivate::resetOperationProgressBar() void ctkDICOMSeriesItemWidgetPrivate::updateRetrieveUIOnStarted() { this->ReferenceSeriesInserterJobUID = ""; + this->ReferenceInstanceInserterJobUID = ""; this->RetrieveFailed = false; this->resetOperationProgressBar(); } @@ -720,8 +674,9 @@ void ctkDICOMSeriesItemWidgetPrivate::updateRetrieveUIOnStarted() //---------------------------------------------------------------------------- void ctkDICOMSeriesItemWidgetPrivate::updateRetrieveUIOnFailed() { - this->RetrieveFailed = true; this->ReferenceSeriesInserterJobUID = ""; + this->ReferenceInstanceInserterJobUID = ""; + this->RetrieveFailed = true; QString file = this->DicomDatabase->fileForInstance(this->CentralFrameSOPInstanceUID); if (file.isEmpty()) @@ -733,6 +688,7 @@ void ctkDICOMSeriesItemWidgetPrivate::updateRetrieveUIOnFailed() int numberOfFrames = instancesList.count(); this->drawThumbnail(file, + this->PatientID, this->StudyInstanceUID, this->SeriesInstanceUID, this->CentralFrameSOPInstanceUID, @@ -750,7 +706,7 @@ void ctkDICOMSeriesItemWidgetPrivate::updateRetrieveUIOnFinished() filesList.removeAll(QString("")); int numberOfFiles = filesList.count(); - if (numberOfFrames > 0 && numberOfFiles < numberOfFrames) + if (numberOfFrames > 1 && numberOfFiles < numberOfFrames) { this->RetrieveFailed = true; this->IsCloud = false; @@ -763,6 +719,7 @@ void ctkDICOMSeriesItemWidgetPrivate::updateRetrieveUIOnFinished() } this->drawThumbnail(this->DicomDatabase->fileForInstance(this->CentralFrameSOPInstanceUID), + this->PatientID, this->StudyInstanceUID, this->SeriesInstanceUID, this->CentralFrameSOPInstanceUID, @@ -776,12 +733,12 @@ void ctkDICOMSeriesItemWidgetPrivate::updateRetrieveUIOnFinished() // ctkDICOMSeriesItemWidget methods //---------------------------------------------------------------------------- -ctkDICOMSeriesItemWidget::ctkDICOMSeriesItemWidget(QWidget* top, QWidget* parent) +ctkDICOMSeriesItemWidget::ctkDICOMSeriesItemWidget(QWidget* parent) : Superclass(parent) , d_ptr(new ctkDICOMSeriesItemWidgetPrivate(*this)) { Q_D(ctkDICOMSeriesItemWidget); - d->init(top); + d->init(); } //---------------------------------------------------------------------------- @@ -821,6 +778,7 @@ CTK_GET_CPP(ctkDICOMSeriesItemWidget, QString, referenceSeriesInserterJobUID, Re CTK_GET_CPP(ctkDICOMSeriesItemWidget, QString, referenceInstanceInserterJobUID, ReferenceInstanceInserterJobUID); CTK_SET_CPP(ctkDICOMSeriesItemWidget, int, setThumbnailSizePixel, ThumbnailSizePixel); CTK_GET_CPP(ctkDICOMSeriesItemWidget, int, thumbnailSizePixel, ThumbnailSizePixel); +CTK_GET_CPP(ctkDICOMSeriesItemWidget, QString, stoppedJobUID, StoppedJobUID); //---------------------------------------------------------------------------- void ctkDICOMSeriesItemWidget::setSeriesDescription(const QString& seriesDescription) @@ -949,7 +907,8 @@ void ctkDICOMSeriesItemWidget::updateGUIFromScheduler(const QVariant& data) if ((td.JobType != ctkDICOMJobResponseSet::JobType::QueryInstances && td.JobType != ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance && - td.JobType != ctkDICOMJobResponseSet::JobType::StoreSOPInstance ) || + td.JobType != ctkDICOMJobResponseSet::JobType::StoreSOPInstance && + td.JobType != ctkDICOMJobResponseSet::JobType::ThumbnailGenerator) || td.SeriesInstanceUID != d->SeriesInstanceUID) { return; @@ -1029,6 +988,8 @@ void ctkDICOMSeriesItemWidget::onJobUserStopped(const QVariant &data) { d->updateRetrieveUIOnFailed(); } + + d->StoppedJobUID = td.JobUID; } } @@ -1056,6 +1017,8 @@ void ctkDICOMSeriesItemWidget::onJobFailed(const QVariant &data) { d->updateRetrieveUIOnFailed(); } + + d->StoppedJobUID = td.JobUID; } } @@ -1065,11 +1028,6 @@ void ctkDICOMSeriesItemWidget::onJobFinished(const QVariant &data) Q_D(ctkDICOMSeriesItemWidget); ctkDICOMJobDetail td = data.value(); - if (td.JobUID.isEmpty()) - { - return; - } - if (td.SeriesInstanceUID == d->SeriesInstanceUID && (td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries || td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance)) @@ -1096,30 +1054,23 @@ void ctkDICOMSeriesItemWidget::onJobFinished(const QVariant &data) d->ReferenceSeriesInserterJobUID = td.ReferenceInserterJobUID; } } - - return; } - - if (td.JobType != ctkDICOMJobResponseSet::JobType::Inserter) + else if (td.JobType == ctkDICOMJobResponseSet::JobType::Inserter && + (d->ReferenceSeriesInserterJobUID == td.JobUID || + d->ReferenceSeriesInserterJobUID == "StorageListener")) { - return; - } - - QStringList instancesList = d->DicomDatabase->instancesForSeries(d->SeriesInstanceUID); - int numberOfFrames = instancesList.count(); - if ((d->ReferenceInstanceInserterJobUID == td.JobUID || - d->ReferenceInstanceInserterJobUID == "StorageListener") && - numberOfFrames == 1) - { - d->SeriesThumbnail->setOperationStatus(ctkThumbnailLabel::Completed); - d->SeriesThumbnail->setStatusIcon(QIcon(":/Icons/accept.svg")); - d->updateRetrieveUIOnFinished(); - } - else if (d->ReferenceSeriesInserterJobUID == td.JobUID || - d->ReferenceSeriesInserterJobUID == "StorageListener") - { - d->ReferenceSeriesInserterJobUID = ""; - d->updateRetrieveUIOnFinished(); + QStringList instancesList = d->DicomDatabase->instancesForSeries(d->SeriesInstanceUID); + if (instancesList.count() == 1) + { + d->SeriesThumbnail->setOperationStatus(ctkThumbnailLabel::Completed); + d->SeriesThumbnail->setStatusIcon(QIcon(":/Icons/accept.svg")); + d->updateRetrieveUIOnFinished(); + } + else + { + d->ReferenceSeriesInserterJobUID = ""; + d->updateRetrieveUIOnFinished(); + } } } @@ -1137,12 +1088,10 @@ void ctkDICOMSeriesItemWidget::onOperationStatusButtonClicked(bool) } else if (status > ctkThumbnailLabel::InProgress) { - ctkDICOMJobDetail queryJobDetail; - queryJobDetail.JobClass = "ctkDICOMQueryJob"; - queryJobDetail.DICOMLevel = ctkDICOMJob::DICOMLevels::Instances; - queryJobDetail.PatientID = d->PatientID; - queryJobDetail.StudyInstanceUID = d->StudyInstanceUID; - queryJobDetail.SeriesInstanceUID = d->SeriesInstanceUID; - d->Scheduler->runJob(queryJobDetail, d->AllowedServers); + if (!d->Scheduler->retryJob(d->StoppedJobUID)) + { + logger.info(QString("Unable to restart job %1 (series level) because the job has been fully cleared from the system. " + "Please initiate a new job if further processing is required.").arg(d->StoppedJobUID)); + } } } diff --git a/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.h b/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.h index 3e9b0b7a61..da29f37365 100644 --- a/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.h +++ b/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.h @@ -29,6 +29,7 @@ #include // ctkDICOMCore includes +#include "ctkDICOMJobResponseSet.h" class ctkDICOMDatabase; class ctkDICOMScheduler; @@ -57,11 +58,11 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMSeriesItemWidget : public QWidget Q_PROPERTY(bool stopJobs READ stopJobs WRITE setStopJobs); Q_PROPERTY(bool raiseJobsPriority READ raiseJobsPriority WRITE setRaiseJobsPriority); Q_PROPERTY(QStringList allowedServers READ allowedServers WRITE setAllowedServers); + Q_PROPERTY(QString stoppedJobUID READ stoppedJobUID); public: typedef QWidget Superclass; - explicit ctkDICOMSeriesItemWidget(QWidget* top = nullptr, - QWidget* parent = nullptr); + explicit ctkDICOMSeriesItemWidget(QWidget* parent = nullptr); virtual ~ctkDICOMSeriesItemWidget(); ///@{ @@ -179,6 +180,9 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMSeriesItemWidget : public QWidget /// (not Python-wrappable). void setDicomDatabase(QSharedPointer dicomDatabase); + /// Last stopped job information operated by this widget + Q_INVOKABLE QString stoppedJobUID() const; + public Q_SLOTS: void generateInstances(bool query = true, bool retrieve = true); void updateGUIFromScheduler(const QVariant&); diff --git a/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.cpp b/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.cpp index a9f8992e83..91ac9bc7a1 100644 --- a/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.cpp @@ -405,6 +405,8 @@ void ctkDICOMServerNodeWidget2Private::disconnectScheduler() q, SLOT(onJobFinished(QList))); QObject::disconnect(this->Scheduler.data(), SIGNAL(jobFailed(QList)), q, SLOT(onJobFailed(QList))); + QObject::disconnect(this->Scheduler.data(), SIGNAL(serverModified(QString)), + q, SLOT(updateGUIFromServerNodes())); } //---------------------------------------------------------------------------- @@ -424,6 +426,8 @@ void ctkDICOMServerNodeWidget2Private::connectScheduler() q, SLOT(onJobFinished(QList))); QObject::connect(this->Scheduler.data(), SIGNAL(jobFailed(QList)), q, SLOT(onJobFailed(QList))); + QObject::connect(this->Scheduler.data(), SIGNAL(serverModified(QString)), + q, SLOT(updateGUIFromServerNodes())); } //---------------------------------------------------------------------------- @@ -724,7 +728,7 @@ int ctkDICOMServerNodeWidget2Private::addServerNode(ctkDICOMServer* server) this->NodeTable->setItem(rowCount, ctkDICOMServerNodeWidget2::StorageColumn, newItem); newItem = new QTableWidgetItem(QString("")); - newItem->setCheckState(server->storageEnabled() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + newItem->setCheckState(server->trustedEnabled() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); this->NodeTable->setItem(rowCount, ctkDICOMServerNodeWidget2::TrustedColumn, newItem); newItem = new QTableWidgetItem(server->callingAETitle()); @@ -1277,6 +1281,34 @@ void ctkDICOMServerNodeWidget2::updateGUIState() d->updateProxyComboBoxes(); } +//---------------------------------------------------------------------------- +void ctkDICOMServerNodeWidget2::updateGUIFromServerNodes() +{ + Q_D(ctkDICOMServerNodeWidget2); + + d->NodeTable->clearContents(); + d->NodeTable->setRowCount(0); + for (int serverIndex = 0; serverIndex < d->Scheduler->serversCount(); ++serverIndex) + { + ctkDICOMServer* server = d->Scheduler->getNthServer(serverIndex); + if (!server) + { + continue; + } + + d->addServerNode(server); + } + + d->SettingsModified = false; + this->updateGUIState(); + QModelIndexList selectedIndexes = d->NodeTable->selectionModel()->selectedIndexes(); + int horizontalScrollBarValue = d->NodeTable->horizontalScrollBar()->value(); + int verticalScrollBarValue = d->NodeTable->verticalScrollBar()->value(); + d->restoreFocus(selectedIndexes, horizontalScrollBarValue, verticalScrollBarValue, false); + + emit serversSettingsChanged(); +} + //---------------------------------------------------------------------------- void ctkDICOMServerNodeWidget2::onItemSelectionChanged() { @@ -1466,7 +1498,7 @@ void ctkDICOMServerNodeWidget2::readSettings() // a dummy example QMap defaultServerNode; defaultServerNode["Name"] = QString("ExampleHost"); - defaultServerNode["Verification"] = tr("unknown"); + defaultServerNode["Verification"] = ctkDICOMServerNodeWidget2::tr("unknown"); defaultServerNode["QueryRetrieveCheckState"] = static_cast(Qt::Unchecked); defaultServerNode["StorageCheckState"] = static_cast(Qt::Unchecked); defaultServerNode["TrustedCheckState"] = static_cast(Qt::Unchecked); @@ -1482,7 +1514,7 @@ void ctkDICOMServerNodeWidget2::readSettings() // the uk example - see http://www.dicomserver.co.uk/ // and http://www.medicalconnections.co.uk/ defaultServerNode["Name"] = QString("MedicalConnections"); - defaultServerNode["Verification"] = tr("unknown"); + defaultServerNode["Verification"] = ctkDICOMServerNodeWidget2::tr("unknown"); defaultServerNode["QueryRetrieveCheckState"] = static_cast(Qt::Unchecked); defaultServerNode["StorageCheckState"] = static_cast(Qt::Unchecked); defaultServerNode["TrustedCheckState"] = static_cast(Qt::Unchecked); @@ -1663,17 +1695,18 @@ ctkDICOMServer* ctkDICOMServerNodeWidget2::getServer(const QString& connectionNa } //---------------------------------------------------------------------------- -void ctkDICOMServerNodeWidget2::addServer(ctkDICOMServer* server) +int ctkDICOMServerNodeWidget2::addServer(ctkDICOMServer* server) { Q_D(ctkDICOMServerNodeWidget2); if (!d->Scheduler) { logger.error("addServer failed, no task pool has been set. \n"); - return; + return -1; } - d->addServerNode(server); + int row = d->addServerNode(server); this->saveSettings(); + return row; } //---------------------------------------------------------------------------- @@ -1754,6 +1787,6 @@ void ctkDICOMServerNodeWidget2::stopAllJobs() } QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); - d->Scheduler->stopAllJobs(true); + d->Scheduler->stopAllJobs(true, false); QApplication::restoreOverrideCursor(); } diff --git a/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.h b/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.h index e766b65420..8811fb4531 100644 --- a/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.h +++ b/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.h @@ -92,7 +92,7 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMServerNodeWidget2 : public QWidget Q_INVOKABLE int serversCount(); Q_INVOKABLE ctkDICOMServer* getNthServer(int id); Q_INVOKABLE ctkDICOMServer* getServer(const QString& connectionName); - Q_INVOKABLE void addServer(ctkDICOMServer* server); + Q_INVOKABLE int addServer(ctkDICOMServer* server); Q_INVOKABLE void removeServer(const QString& connectionName); Q_INVOKABLE void removeNthServer(int id); Q_INVOKABLE void removeAllServers(); @@ -119,6 +119,7 @@ public Q_SLOTS: void saveSettings(); void onRestoreDefaultServers(); void updateGUIState(); + void updateGUIFromServerNodes(); void onItemSelectionChanged(); void onSettingsModified(); void onCellSettingsModified(int row, int column); diff --git a/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp index d9bfb1b285..546655fed8 100644 --- a/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp @@ -64,9 +64,7 @@ class ctkDICOMStudyItemWidgetPrivate : public Ui_ctkDICOMStudyItemWidget ctkDICOMStudyItemWidgetPrivate(ctkDICOMStudyItemWidget& obj); ~ctkDICOMStudyItemWidgetPrivate(); - void init(QWidget* top, QWidget* parent); - void connectToTop(); - void disconnectFromTop(); + void init(QWidget* parent); void updateColumnsWidths(); void createSeries(); int getScreenWidth(); @@ -78,14 +76,10 @@ class ctkDICOMStudyItemWidgetPrivate : public Ui_ctkDICOMStudyItemWidget QString FilteringSeriesDescription; QStringList FilteringModalities; - QStringList AllowedServers; - ctkDICOMStudyItemWidget::OperationStatus Status; QSharedPointer DicomDatabase; QSharedPointer Scheduler; - QSharedPointer PatientWidget; QSharedPointer VisualDICOMBrowser; - QMap Connections; ctkDICOMStudyItemWidget::ThumbnailSizeOption ThumbnailSize; int ThumbnailSizePixel; @@ -93,6 +87,10 @@ class ctkDICOMStudyItemWidgetPrivate : public Ui_ctkDICOMStudyItemWidget QString StudyInstanceUID; QString StudyItem; + QStringList AllowedServers; + ctkDICOMStudyItemWidget::OperationStatus Status; + QString StoppedJobUID; + bool IsGUIUpdating; bool QueryOn; bool RetrieveOn; @@ -115,6 +113,7 @@ ctkDICOMStudyItemWidgetPrivate::ctkDICOMStudyItemWidgetPrivate(ctkDICOMStudyItem this->AllowedServers = QStringList(); this->Status = ctkDICOMStudyItemWidget::NoOperation; + this->StoppedJobUID = ""; this->DicomDatabase = nullptr; this->Scheduler = nullptr; @@ -129,7 +128,6 @@ ctkDICOMStudyItemWidgetPrivate::ctkDICOMStudyItemWidgetPrivate(ctkDICOMStudyItem //---------------------------------------------------------------------------- ctkDICOMStudyItemWidgetPrivate::~ctkDICOMStudyItemWidgetPrivate() { - this->disconnectFromTop(); for (int row = 0; row < this->SeriesListTableWidget->rowCount(); row++) { for (int column = 0; column < this->SeriesListTableWidget->columnCount(); column++) @@ -148,13 +146,11 @@ ctkDICOMStudyItemWidgetPrivate::~ctkDICOMStudyItemWidgetPrivate() } //---------------------------------------------------------------------------- -void ctkDICOMStudyItemWidgetPrivate::init(QWidget* top, QWidget* parent) +void ctkDICOMStudyItemWidgetPrivate::init(QWidget* parent) { Q_Q(ctkDICOMStudyItemWidget); this->setupUi(q); - this->PatientWidget = QSharedPointer(top, skipDelete); - this->connectToTop(); this->VisualDICOMBrowser = QSharedPointer(parent, skipDelete); this->StudyDescriptionTextBrowser->hide(); @@ -170,50 +166,6 @@ void ctkDICOMStudyItemWidgetPrivate::init(QWidget* top, QWidget* parent) q, SLOT(onOperationStatusButtonClicked(bool))); } -//------------------------------------------------------------------------------ -void ctkDICOMStudyItemWidgetPrivate::connectToTop() -{ - Q_Q(ctkDICOMStudyItemWidget); - if (!this->PatientWidget) - { - return; - } - - QMetaObject::Connection progressConnection = - QObject::connect(this->PatientWidget.data(), SIGNAL(progressJobDetail(QVariant)), - q, SLOT(updateGUIFromScheduler(QVariant))); - QMetaObject::Connection startedConnection = - QObject::connect(this->PatientWidget.data(), SIGNAL(jobStarted(QVariant)), - q, SLOT(onJobStarted(QVariant))); - QMetaObject::Connection userStoppedConnection = - QObject::connect(this->PatientWidget.data(), SIGNAL(jobUserStopped(QVariant)), - q, SLOT(onJobUserStopped(QVariant))); - QMetaObject::Connection failedConnection = - QObject::connect(this->PatientWidget.data(), SIGNAL(jobFailed(QVariant)), - q, SLOT(onJobFailed(QVariant))); - QMetaObject::Connection finishedConnection = - QObject::connect(this->PatientWidget.data(), SIGNAL(jobFinished(QVariant)), - q, SLOT(onJobFinished(QVariant))); - this->Connections = - { - {"progress", progressConnection}, - {"started", startedConnection}, - {"userStopped", userStoppedConnection}, - {"finished", finishedConnection}, - {"failed", failedConnection} - }; -} - -//------------------------------------------------------------------------------ -void ctkDICOMStudyItemWidgetPrivate::disconnectFromTop() -{ - QObject::disconnect(this->Connections.value("progress")); - QObject::disconnect(this->Connections.value("started")); - QObject::disconnect(this->Connections.value("userStopped")); - QObject::disconnect(this->Connections.value("finished")); - QObject::disconnect(this->Connections.value("failed")); -} - //------------------------------------------------------------------------------ void ctkDICOMStudyItemWidgetPrivate::updateColumnsWidths() { @@ -452,12 +404,12 @@ ctkDICOMSeriesItemWidget* ctkDICOMStudyItemWidgetPrivate::isSeriesItemAlreadyAdd // ctkDICOMStudyItemWidget methods //---------------------------------------------------------------------------- -ctkDICOMStudyItemWidget::ctkDICOMStudyItemWidget(QWidget* top, QWidget* parent) +ctkDICOMStudyItemWidget::ctkDICOMStudyItemWidget(QWidget* parent) : Superclass(parent) , d_ptr(new ctkDICOMStudyItemWidgetPrivate(*this)) { Q_D(ctkDICOMStudyItemWidget); - d->init(top, parent); + d->init(parent); } //---------------------------------------------------------------------------- @@ -483,7 +435,7 @@ CTK_GET_CPP(ctkDICOMStudyItemWidget, QString, filteringSeriesDescription, Filter CTK_SET_CPP(ctkDICOMStudyItemWidget, const QStringList&, setFilteringModalities, FilteringModalities); CTK_GET_CPP(ctkDICOMStudyItemWidget, QStringList, filteringModalities, FilteringModalities); CTK_GET_CPP(ctkDICOMStudyItemWidget, int, filteredSeriesCount, FilteredSeriesCount); - +CTK_GET_CPP(ctkDICOMStudyItemWidget, QString, stoppedJobUID, StoppedJobUID); //---------------------------------------------------------------------------- void ctkDICOMStudyItemWidget::setTitle(const QString& title) @@ -698,7 +650,7 @@ ctkDICOMSeriesItemWidget* ctkDICOMStudyItemWidget::addSeriesItemWidget(int table } QString seriesNumber = d->DicomDatabase->fieldForSeries("SeriesNumber", seriesItem); - ctkDICOMSeriesItemWidget* seriesItemWidget = new ctkDICOMSeriesItemWidget(this); + ctkDICOMSeriesItemWidget* seriesItemWidget = new ctkDICOMSeriesItemWidget(); seriesItemWidget->setSeriesItem(seriesItem); seriesItemWidget->setPatientID(d->PatientID); seriesItemWidget->setStudyInstanceUID(d->StudyInstanceUID); @@ -761,6 +713,50 @@ void ctkDICOMStudyItemWidget::removeSeriesItemWidget(const QString& seriesItem) } } +//------------------------------------------------------------------------------ +ctkDICOMSeriesItemWidget *ctkDICOMStudyItemWidget::seriesItemWidgetBySeriesItem(const QString &seriesItem) +{ +Q_D(ctkDICOMStudyItemWidget); + + for (int row = 0; row < d->SeriesListTableWidget->rowCount(); row++) + { + for (int column = 0; column < d->SeriesListTableWidget->columnCount(); column++) + { + ctkDICOMSeriesItemWidget* seriesItemWidget = + qobject_cast(d->SeriesListTableWidget->cellWidget(row, column)); + if (!seriesItemWidget || seriesItemWidget->seriesItem() != seriesItem) + { + continue; + } + + return seriesItemWidget; + } + } + return nullptr; +} + +//------------------------------------------------------------------------------ +ctkDICOMSeriesItemWidget *ctkDICOMStudyItemWidget::seriesItemWidgetBySeriesInstanceUID(const QString &seriesInstanceUID) +{ + Q_D(ctkDICOMStudyItemWidget); + + for (int row = 0; row < d->SeriesListTableWidget->rowCount(); row++) + { + for (int column = 0; column < d->SeriesListTableWidget->columnCount(); column++) + { + ctkDICOMSeriesItemWidget* seriesItemWidget = + qobject_cast(d->SeriesListTableWidget->cellWidget(row, column)); + if (!seriesItemWidget || seriesItemWidget->seriesInstanceUID() != seriesInstanceUID) + { + continue; + } + + return seriesItemWidget; + } + } + return nullptr; +} + //------------------------------------------------------------------------------ ctkCollapsibleGroupBox* ctkDICOMStudyItemWidget::collapsibleGroupBox() { @@ -803,14 +799,27 @@ void ctkDICOMStudyItemWidget::updateGUIFromScheduler(const QVariant& data) return; } - emit this->progressJobDetail(data); - - if (td.JobType != ctkDICOMJobResponseSet::JobType::QuerySeries) + if (td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries) { - return; + d->createSeries(); + } + else if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryInstances || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance || + td.JobType == ctkDICOMJobResponseSet::JobType::StoreSOPInstance || + td.JobType == ctkDICOMJobResponseSet::JobType::ThumbnailGenerator || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries) + { + ctkDICOMSeriesItemWidget* seriesItemWidget = this->seriesItemWidgetBySeriesInstanceUID(td.SeriesInstanceUID); + if (seriesItemWidget) + { + seriesItemWidget->updateGUIFromScheduler(data); + if (td.JobType == ctkDICOMJobResponseSet::JobType::StoreSOPInstance || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries) + { + seriesItemWidget->updateSeriesProgressBar(data); + } + } } - - d->createSeries(); } //------------------------------------------------------------------------------ @@ -825,16 +834,22 @@ void ctkDICOMStudyItemWidget::onJobStarted(const QVariant &data) return; } - emit this->jobStarted(data); - - if (td.JobType != ctkDICOMJobResponseSet::JobType::QuerySeries) + if (td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries) { - return; + d->Status = ctkDICOMStudyItemWidget::InProgress; + d->OperationStatusPushButton->show(); + d->OperationStatusPushButton->setIcon(QIcon(":/Icons/pending.svg")); + } + else if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryInstances || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries) + { + ctkDICOMSeriesItemWidget* seriesItemWidget = this->seriesItemWidgetBySeriesInstanceUID(td.SeriesInstanceUID); + if (seriesItemWidget) + { + seriesItemWidget->onJobStarted(data); + } } - - d->Status = ctkDICOMStudyItemWidget::InProgress; - d->OperationStatusPushButton->show(); - d->OperationStatusPushButton->setIcon(QIcon(":/Icons/pending.svg")); } //------------------------------------------------------------------------------ @@ -849,15 +864,22 @@ void ctkDICOMStudyItemWidget::onJobUserStopped(const QVariant &data) return; } - emit this->jobUserStopped(data); - - if (td.JobType != ctkDICOMJobResponseSet::JobType::QuerySeries) + if (td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries) { - return; + d->Status = ctkDICOMStudyItemWidget::Failed; + d->OperationStatusPushButton->setIcon(QIcon(":/Icons/error_red.svg")); + d->StoppedJobUID = td.JobUID; + } + else if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryInstances || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries) + { + ctkDICOMSeriesItemWidget* seriesItemWidget = this->seriesItemWidgetBySeriesInstanceUID(td.SeriesInstanceUID); + if (seriesItemWidget) + { + seriesItemWidget->onJobUserStopped(data); + } } - - d->Status = ctkDICOMStudyItemWidget::Failed; - d->OperationStatusPushButton->setIcon(QIcon(":/Icons/error_red.svg")); } //------------------------------------------------------------------------------ @@ -872,15 +894,22 @@ void ctkDICOMStudyItemWidget::onJobFailed(const QVariant &data) return; } - emit this->jobFailed(data); - - if (td.JobType != ctkDICOMJobResponseSet::JobType::QuerySeries) + if (td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries) { - return; + d->Status = ctkDICOMStudyItemWidget::Failed; + d->OperationStatusPushButton->setIcon(QIcon(":/Icons/error_red.svg")); + d->StoppedJobUID = td.JobUID; + } + else if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryInstances || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries) + { + ctkDICOMSeriesItemWidget* seriesItemWidget = this->seriesItemWidgetBySeriesInstanceUID(td.SeriesInstanceUID); + if (seriesItemWidget) + { + seriesItemWidget->onJobFailed(data); + } } - - d->Status = ctkDICOMStudyItemWidget::Failed; - d->OperationStatusPushButton->setIcon(QIcon(":/Icons/error_red.svg")); } //------------------------------------------------------------------------------ @@ -895,15 +924,20 @@ void ctkDICOMStudyItemWidget::onJobFinished(const QVariant &data) return; } - emit this->jobFinished(data); - - if (td.JobType != ctkDICOMJobResponseSet::JobType::QuerySeries) + if (td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries) { - return; + d->Status = ctkDICOMStudyItemWidget::Completed; + d->OperationStatusPushButton->setIcon(QIcon(":/Icons/accept.svg")); + } + else if (td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance) + { + ctkDICOMSeriesItemWidget* seriesItemWidget = this->seriesItemWidgetBySeriesInstanceUID(td.SeriesInstanceUID); + if (seriesItemWidget) + { + seriesItemWidget->onJobFinished(data); + } } - - d->Status = ctkDICOMStudyItemWidget::Completed; - d->OperationStatusPushButton->setIcon(QIcon(":/Icons/accept.svg")); } //------------------------------------------------------------------------------ @@ -925,12 +959,10 @@ void ctkDICOMStudyItemWidget::onOperationStatusButtonClicked(bool) } else if (status > ctkDICOMStudyItemWidget::InProgress) { - ctkDICOMJobDetail queryJobDetail; - queryJobDetail.JobClass = "ctkDICOMQueryJob"; - queryJobDetail.DICOMLevel = ctkDICOMJob::DICOMLevels::Series; - queryJobDetail.PatientID = d->PatientID; - queryJobDetail.StudyInstanceUID = d->StudyInstanceUID; - - d->Scheduler->runJob(queryJobDetail, d->AllowedServers); + if (!d->Scheduler->retryJob(d->StoppedJobUID)) + { + logger.info(QString("Unable to restart job %1 (study level) because the job has been fully cleared from the system. " + "Please initiate a new job if further processing is required.").arg(d->StoppedJobUID)); + } } } diff --git a/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.h b/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.h index 00874c6611..97ec115109 100644 --- a/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.h +++ b/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.h @@ -64,11 +64,11 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMStudyItemWidget : public QWidget Q_PROPERTY(int filteredSeriesCount READ filteredSeriesCount); Q_PROPERTY(QStringList allowedServers READ allowedServers WRITE setAllowedServers); Q_PROPERTY(OperationStatus operationStatus READ operationStatus WRITE setOperationStatus); + Q_PROPERTY(QString stoppedJobUID READ stoppedJobUID); public: typedef QWidget Superclass; - explicit ctkDICOMStudyItemWidget(QWidget* top = nullptr, - QWidget* parent = nullptr); + explicit ctkDICOMStudyItemWidget(QWidget* parent = nullptr); virtual ~ctkDICOMStudyItemWidget(); ///@{ @@ -157,21 +157,6 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMStudyItemWidget : public QWidget QStringList allowedServers() const; ///@} - enum OperationStatus - { - NoOperation = 0, - InProgress, - Completed, - Failed, - }; - - ///@{ - /// Set the operation status - /// NoOperation by default - void setOperationStatus(const OperationStatus& status); - OperationStatus operationStatus() const; - ///@} - /// Return the scheduler. Q_INVOKABLE ctkDICOMScheduler* scheduler() const; /// Return the scheduler as a shared pointer @@ -200,6 +185,7 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMStudyItemWidget : public QWidget /// Return all the series item widgets for the study Q_INVOKABLE QList seriesItemWidgetsList() const; + ///@{ /// Add/Remove Series item widget Q_INVOKABLE ctkDICOMSeriesItemWidget* addSeriesItemWidget(int tableIndex, const QString& seriesItem, @@ -207,10 +193,31 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMStudyItemWidget : public QWidget const QString& modality, const QString& seriesDescription); Q_INVOKABLE void removeSeriesItemWidget(const QString& seriesItem); + Q_INVOKABLE ctkDICOMSeriesItemWidget* seriesItemWidgetBySeriesItem(const QString& seriesItem); + Q_INVOKABLE ctkDICOMSeriesItemWidget* seriesItemWidgetBySeriesInstanceUID(const QString& seriesInstanceUID); + ///@} /// Collapsible group box. Q_INVOKABLE ctkCollapsibleGroupBox* collapsibleGroupBox(); + enum OperationStatus + { + NoOperation = 0, + InProgress, + Completed, + Failed, + }; + + ///@{ + /// Set the operation status + /// NoOperation by default + void setOperationStatus(const OperationStatus& status); + OperationStatus operationStatus() const; + ///@} + + /// Last stopped job information operated by this widget + Q_INVOKABLE QString stoppedJobUID() const; + public Q_SLOTS: void generateSeries(bool query = true, bool retrieve = true); void updateGUIFromScheduler(const QVariant&); @@ -224,12 +231,6 @@ public Q_SLOTS: Q_SIGNALS: /// Emitted when the GUI finished to update after a series query. void updateGUIFinished(); - /// Propagate jobs signals to the tree - void jobStarted(QVariant); - void jobUserStopped(QVariant); - void jobFinished(QVariant); - void jobFailed(QVariant); - void progressJobDetail(QVariant); protected: QScopedPointer d_ptr; diff --git a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp index c720752def..6b29f160ca 100644 --- a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp @@ -1960,7 +1960,7 @@ ctkDICOMServer* ctkDICOMVisualBrowserWidget::getServer(const QString& connection } //---------------------------------------------------------------------------- -void ctkDICOMVisualBrowserWidget::addServer(ctkDICOMServer* server) +int ctkDICOMVisualBrowserWidget::addServer(ctkDICOMServer* server) { Q_D(ctkDICOMVisualBrowserWidget); return d->ServerNodeWidget->addServer(server); @@ -2152,14 +2152,60 @@ void ctkDICOMVisualBrowserWidget::removePatientItemWidget(const QString& patient } //------------------------------------------------------------------------------ -ctkDICOMPatientItemWidget* ctkDICOMVisualBrowserWidget::getPatientItemWidgetByPatientName(const QString& patientName) +ctkDICOMPatientItemWidget *ctkDICOMVisualBrowserWidget::getPatientItemWidgetByPatientItem(const QString &patientItem) { Q_D(ctkDICOMVisualBrowserWidget); - if (!d->DicomDatabase) + + for (int patientIndex = 0; patientIndex < d->PatientsTabWidget->count(); ++patientIndex) { - return nullptr; + ctkDICOMPatientItemWidget* patientItemWidget = + qobject_cast(d->PatientsTabWidget->widget(patientIndex)); + if (!patientItemWidget) + { + continue; + } + + if (patientItemWidget->patientItem() != patientItem) + { + continue; + } + + return patientItemWidget; + } + + return nullptr; +} + +//------------------------------------------------------------------------------ +ctkDICOMPatientItemWidget *ctkDICOMVisualBrowserWidget::getPatientItemWidgetByPatientID(const QString &patientID) +{ + Q_D(ctkDICOMVisualBrowserWidget); + + for (int patientIndex = 0; patientIndex < d->PatientsTabWidget->count(); ++patientIndex) + { + ctkDICOMPatientItemWidget* patientItemWidget = + qobject_cast(d->PatientsTabWidget->widget(patientIndex)); + if (!patientItemWidget) + { + continue; + } + + if (patientItemWidget->patientID() != patientID) + { + continue; + } + + return patientItemWidget; } + return nullptr; +} + +//------------------------------------------------------------------------------ +ctkDICOMPatientItemWidget* ctkDICOMVisualBrowserWidget::getPatientItemWidgetByPatientName(const QString& patientName) +{ + Q_D(ctkDICOMVisualBrowserWidget); + for (int patientIndex = 0; patientIndex < d->PatientsTabWidget->count(); ++patientIndex) { ctkDICOMPatientItemWidget* patientItemWidget = @@ -2169,9 +2215,7 @@ ctkDICOMPatientItemWidget* ctkDICOMVisualBrowserWidget::getPatientItemWidgetByPa continue; } - QString tempPatientName = d->DicomDatabase->fieldForPatient("PatientsName", patientItemWidget->patientItem()); - tempPatientName.replace(R"(^)", R"( )"); - if (tempPatientName != patientName) + if (patientItemWidget->patientName() != patientName) { continue; } @@ -2182,6 +2226,13 @@ ctkDICOMPatientItemWidget* ctkDICOMVisualBrowserWidget::getPatientItemWidgetByPa return nullptr; } +//------------------------------------------------------------------------------ +QTabWidget* ctkDICOMVisualBrowserWidget::patientsTabWidget() +{ + Q_D(ctkDICOMVisualBrowserWidget); + return d->PatientsTabWidget; +} + //------------------------------------------------------------------------------ int ctkDICOMVisualBrowserWidget::patientsAddedDuringImport() { @@ -2279,11 +2330,11 @@ void ctkDICOMVisualBrowserWidget::setDatabaseDirectory(const QString& directory) if (!QDir(absDirectory).exists() || (!ctk::isDirEmpty(QDir(absDirectory)) && !QFile(databaseFileName).exists())) { - logger.warn(tr("Database folder does not contain ctkDICOM.sql file: ") + absDirectory + "\n"); + logger.warn(ctkDICOMVisualBrowserWidget::tr("Database folder does not contain ctkDICOM.sql file: ") + absDirectory + "\n"); d->DatabaseDirectoryProblemFrame->show(); d->DatabaseDirectoryProblemLabel->setText( //: %1 is the folder path - tr("No valid DICOM database found in folder %1.").arg(absDirectory) + ctkDICOMVisualBrowserWidget::tr("No valid DICOM database found in folder %1.").arg(absDirectory) ); d->UpdateDatabaseButton->hide(); d->CreateNewDatabaseButton->show(); @@ -2306,12 +2357,12 @@ void ctkDICOMVisualBrowserWidget::setDatabaseDirectory(const QString& directory) } if (!databaseOpenSuccess || d->DicomDatabase->schemaVersionLoaded().isEmpty()) { - logger.warn(tr("Database error: %1 \n").arg(d->DicomDatabase->lastError())); + logger.warn(ctkDICOMVisualBrowserWidget::tr("Database error: %1 \n").arg(d->DicomDatabase->lastError())); d->DicomDatabase->closeDatabase(); d->DatabaseDirectoryProblemFrame->show(); d->DatabaseDirectoryProblemLabel->setText( //: %1 is the folder path - tr("No valid DICOM database found in folder %1.").arg(absDirectory) + ctkDICOMVisualBrowserWidget::tr("No valid DICOM database found in folder %1.").arg(absDirectory) ); d->UpdateDatabaseButton->hide(); d->CreateNewDatabaseButton->show(); @@ -2324,13 +2375,13 @@ void ctkDICOMVisualBrowserWidget::setDatabaseDirectory(const QString& directory) { if (d->DicomDatabase->schemaVersionLoaded() != d->DicomDatabase->schemaVersion()) { - logger.warn(tr("Database version mismatch: version of selected database = %1, version required = %2 \n") + logger.warn(ctkDICOMVisualBrowserWidget::tr("Database version mismatch: version of selected database = %1, version required = %2 \n") .arg(d->DicomDatabase->schemaVersionLoaded()).arg(d->DicomDatabase->schemaVersion())); d->DicomDatabase->closeDatabase(); d->DatabaseDirectoryProblemFrame->show(); d->DatabaseDirectoryProblemLabel->setText( //: %1 is the folder path - tr("Incompatible DICOM database version found in folder %1.").arg(absDirectory) + ctkDICOMVisualBrowserWidget::tr("Incompatible DICOM database version found in folder %1.").arg(absDirectory) ); d->UpdateDatabaseButton->show(); d->CreateNewDatabaseButton->show(); @@ -2547,7 +2598,7 @@ void ctkDICOMVisualBrowserWidget::createNewDatabaseDirectory() d->DatabaseDirectoryProblemFrame->show(); d->DatabaseDirectoryProblemLabel->setText( //: %1 is the folder path - tr("Failed to create new database in folder %1.").arg(QDir(baseFolder).absolutePath()) + ctkDICOMVisualBrowserWidget::tr("Failed to create new database in folder %1.").arg(QDir(baseFolder).absolutePath()) ); d->UpdateDatabaseButton->hide(); d->CreateNewDatabaseButton->show(); @@ -2888,7 +2939,7 @@ void ctkDICOMVisualBrowserWidget::onShowPatients() { d->setBackgroundColorToFilterWidgets(true); - d->WarningPushButton->setText(tr("No patients have been found in the local database.")); + d->WarningPushButton->setText(ctkDICOMVisualBrowserWidget::tr("No patients have been found in the local database.")); d->WarningPushButton->show(); d->patientsTabMenuToolButton->hide(); return; @@ -2953,10 +3004,10 @@ void ctkDICOMVisualBrowserWidget::onQueryPatients() { d->setBackgroundColorToFilterWidgets(true); - d->WarningPushButton->setText(tr("No filters or query/retrieve servers have been set and" - " no patients have been found in the local database." - "\nPlease set at least one filter to query the servers and " - "check that at least one server has the Query/Retrieve property toggled.")); + d->WarningPushButton->setText(ctkDICOMVisualBrowserWidget::tr("No filters or query/retrieve servers have been set and" + " no patients have been found in the local database." + "\nPlease set at least one filter to query the servers and " + "check that at least one server has the Query/Retrieve property toggled.")); d->WarningPushButton->show(); d->patientsTabMenuToolButton->hide(); return; @@ -3025,7 +3076,23 @@ void ctkDICOMVisualBrowserWidget::updateGUIFromScheduler(QList datas) d->updateFiltersWarnings(); continue; } - else if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryStudies || + + if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryStudies || + td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries || + td.JobType == ctkDICOMJobResponseSet::JobType::QueryInstances || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance || + td.JobType == ctkDICOMJobResponseSet::JobType::StoreSOPInstance || + td.JobType == ctkDICOMJobResponseSet::JobType::ThumbnailGenerator || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries) + { + ctkDICOMPatientItemWidget* patientItemWidget = this->getPatientItemWidgetByPatientID(td.PatientID); + if (patientItemWidget) + { + patientItemWidget->updateGUIFromScheduler(data); + } + } + + if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryStudies || td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries) { d->updateFiltersWarnings(); @@ -3039,7 +3106,7 @@ void ctkDICOMVisualBrowserWidget::updateGUIFromScheduler(QList datas) d->updateFiltersWarnings(); if (td.NumberOfDataSets == 0) { - d->WarningPushButton->setText(tr("The patients query provided no results. Please refine your filters.")); + d->WarningPushButton->setText(ctkDICOMVisualBrowserWidget::tr("The patients query provided no results. Please refine your filters.")); d->WarningPushButton->show(); d->SearchMenuButton->setIcon(QIcon(":/Icons/query_failed.svg")); } @@ -3086,6 +3153,18 @@ void ctkDICOMVisualBrowserWidget::onJobStarted(QList datas) } d->PatientsTabWidget->setTabIcon(patientIndex, QIcon(":/Icons/patient_pending.svg")); } + + if (td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries || + td.JobType == ctkDICOMJobResponseSet::JobType::QueryInstances || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries) + { + ctkDICOMPatientItemWidget* patientItemWidget = this->getPatientItemWidgetByPatientID(td.PatientID); + if (patientItemWidget) + { + patientItemWidget->onJobStarted(data); + } + } } } @@ -3119,6 +3198,19 @@ void ctkDICOMVisualBrowserWidget::onJobUserStopped(QList datas) } d->PatientsTabWidget->setTabIcon(patientIndex, QIcon(":/Icons/patient_failed.svg")); } + + if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryStudies || + td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries || + td.JobType == ctkDICOMJobResponseSet::JobType::QueryInstances || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries) + { + ctkDICOMPatientItemWidget* patientItemWidget = this->getPatientItemWidgetByPatientID(td.PatientID); + if (patientItemWidget) + { + patientItemWidget->onJobUserStopped(data); + } + } } } @@ -3139,7 +3231,7 @@ void ctkDICOMVisualBrowserWidget::onJobFailed(QList datas) { d->updateFiltersWarnings(); d->SearchMenuButton->setIcon(QIcon(":/Icons/query_failed.svg")); - d->WarningPushButton->setText(tr("The patients query failed. Please check the servers settings.")); + d->WarningPushButton->setText(ctkDICOMVisualBrowserWidget::tr("The patients query failed. Please check the servers settings.")); d->WarningPushButton->show(); } else if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryStudies) @@ -3154,6 +3246,19 @@ void ctkDICOMVisualBrowserWidget::onJobFailed(QList datas) } d->PatientsTabWidget->setTabIcon(patientIndex, QIcon(":/Icons/patient_failed.svg")); } + + if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryStudies || + td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries || + td.JobType == ctkDICOMJobResponseSet::JobType::QueryInstances || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries) + { + ctkDICOMPatientItemWidget* patientItemWidget = this->getPatientItemWidgetByPatientID(td.PatientID); + if (patientItemWidget) + { + patientItemWidget->onJobFailed(data); + } + } } } @@ -3187,6 +3292,29 @@ void ctkDICOMVisualBrowserWidget::onJobFinished(QList datas) } d->PatientsTabWidget->setTabIcon(patientIndex, QIcon(":/Icons/patient_success.svg")); } + + if (td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance || + td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries) + { + ctkDICOMPatientItemWidget* patientItemWidget = this->getPatientItemWidgetByPatientID(td.PatientID); + if (patientItemWidget) + { + patientItemWidget->onJobFinished(data); + } + } + else if (td.JobType == ctkDICOMJobResponseSet::JobType::Inserter) + { + for (int patientIndex = 0; patientIndex < d->PatientsTabWidget->count(); ++patientIndex) + { + ctkDICOMPatientItemWidget* patientItemWidget = + qobject_cast(d->PatientsTabWidget->widget(patientIndex)); + if (patientItemWidget) + { + patientItemWidget->onInserterJobFinished(data); + } + } + } } } @@ -3222,12 +3350,11 @@ void ctkDICOMVisualBrowserWidget::onOperationStatusTabBarItemClicked(int index) } else if (status > ctkDICOMPatientItemWidget::InProgress) { - ctkDICOMJobDetail queryJobDetail; - queryJobDetail.JobClass = "ctkDICOMQueryJob"; - queryJobDetail.DICOMLevel = ctkDICOMJob::DICOMLevels::Studies; - queryJobDetail.PatientID = patientItemWidget->patientID(); - - d->Scheduler->runJob(queryJobDetail, patientItemWidget->allowedServers()); + if (!d->Scheduler->retryJob(patientItemWidget->stoppedJobUID())) + { + logger.info(QString("Unable to restart Job job %1 (patient level) because the job has been fully cleared from the system. " + "Please initiate a new job if further processing is required.").arg(patientItemWidget->stoppedJobUID())); + } } } @@ -3281,24 +3408,24 @@ void ctkDICOMVisualBrowserWidget::showPatientContextMenu(const QPoint& point) QPoint globalPos = patientItemWidget->mapToGlobal(point); QMenu* patientMenu = new QMenu(); - QString loadString = tr("Load patient files"); + QString loadString = ctkDICOMVisualBrowserWidget::tr("Load patient files"); QAction* loadAction = new QAction(loadString, patientMenu); patientMenu->addAction(loadAction); - QString metadataString = tr("View patient DICOM metadata"); + QString metadataString = ctkDICOMVisualBrowserWidget::tr("View patient DICOM metadata"); QAction* metadataAction = new QAction(metadataString, patientMenu); patientMenu->addAction(metadataAction); - QString deleteString = tr("Delete patient from local database"); + QString deleteString = ctkDICOMVisualBrowserWidget::tr("Delete patient from local database"); QAction* deleteAction = new QAction(deleteString, patientMenu); patientMenu->addAction(deleteAction); deleteAction->setVisible(this->isDeleteActionVisible()); - QString exportString = tr("Export patient to file system"); + QString exportString = ctkDICOMVisualBrowserWidget::tr("Export patient to file system"); QAction* exportAction = new QAction(exportString, patientMenu); patientMenu->addAction(exportAction); - QString sendString = tr("Send patient to DICOM server"); + QString sendString = ctkDICOMVisualBrowserWidget::tr("Send patient to DICOM server"); QAction* sendAction = new QAction(sendString, patientMenu); sendAction->setVisible(this->isSendActionVisible()); patientMenu->addAction(sendAction); @@ -3372,34 +3499,34 @@ void ctkDICOMVisualBrowserWidget::showStudyContextMenu(const QPoint& point) QPoint globalPos = studyItemWidget->mapToGlobal(point); QMenu* studyMenu = new QMenu(); - QString loadString = numberOfSelectedStudies == 1 ? tr("Load study") : - tr("Load %1 studies").arg(numberOfSelectedStudies); + QString loadString = numberOfSelectedStudies == 1 ? ctkDICOMVisualBrowserWidget::tr("Load study") : + ctkDICOMVisualBrowserWidget::tr("Load %1 studies").arg(numberOfSelectedStudies); QAction* loadAction = new QAction(loadString, studyMenu); studyMenu->addAction(loadAction); - QString metadataString = numberOfSelectedStudies == 1 ? tr("View study DICOM metadata") : - tr("View %1 studies DICOM metadata").arg(numberOfSelectedStudies); + QString metadataString = numberOfSelectedStudies == 1 ? ctkDICOMVisualBrowserWidget::tr("View study DICOM metadata") : + ctkDICOMVisualBrowserWidget::tr("View %1 studies DICOM metadata").arg(numberOfSelectedStudies); QAction* metadataAction = new QAction(metadataString, studyMenu); studyMenu->addAction(metadataAction); - QString forceRetrieveString = numberOfSelectedStudies == 1 ? tr("Force retrieve series") : - tr("Force retrieve series for %1 studies").arg(numberOfSelectedStudies); + QString forceRetrieveString = numberOfSelectedStudies == 1 ? ctkDICOMVisualBrowserWidget::tr("Force retrieve series") : + ctkDICOMVisualBrowserWidget::tr("Force retrieve series for %1 studies").arg(numberOfSelectedStudies); QAction *forceRetrieveAction = new QAction(forceRetrieveString, studyMenu); studyMenu->addAction(forceRetrieveAction); - QString deleteString = numberOfSelectedStudies == 1 ? tr("Delete study from local database") : - tr("Delete %1 studies from local database").arg(numberOfSelectedStudies); + QString deleteString = numberOfSelectedStudies == 1 ? ctkDICOMVisualBrowserWidget::tr("Delete study from local database") : + ctkDICOMVisualBrowserWidget::tr("Delete %1 studies from local database").arg(numberOfSelectedStudies); QAction* deleteAction = new QAction(deleteString, studyMenu); studyMenu->addAction(deleteAction); deleteAction->setVisible(this->isDeleteActionVisible()); - QString exportString = numberOfSelectedStudies == 1 ? tr("Export study to file system") : - tr("Export %1 studies to file system").arg(numberOfSelectedStudies); + QString exportString = numberOfSelectedStudies == 1 ? ctkDICOMVisualBrowserWidget::tr("Export study to file system") : + ctkDICOMVisualBrowserWidget::tr("Export %1 studies to file system").arg(numberOfSelectedStudies); QAction* exportAction = new QAction(exportString, studyMenu); studyMenu->addAction(exportAction); - QString sendString = numberOfSelectedStudies == 1 ? tr("Send study to DICOM server") : - tr("Send %1 studies to DICOM server").arg(numberOfSelectedStudies); + QString sendString = numberOfSelectedStudies == 1 ? ctkDICOMVisualBrowserWidget::tr("Send study to DICOM server") : + ctkDICOMVisualBrowserWidget::tr("Send %1 studies to DICOM server").arg(numberOfSelectedStudies); QAction* sendAction = new QAction(sendString, studyMenu); sendAction->setVisible(this->isSendActionVisible()); studyMenu->addAction(sendAction); @@ -3482,34 +3609,34 @@ void ctkDICOMVisualBrowserWidget::showSeriesContextMenu(const QPoint& point) QPoint globalPos = selectedSeriesItemWidget->mapToGlobal(point); QMenu* seriesMenu = new QMenu(); - QString loadString = numberOfSelectedSeries == 1 ? tr("Load series") : - tr("Load %1 series").arg(numberOfSelectedSeries); + QString loadString = numberOfSelectedSeries == 1 ? ctkDICOMVisualBrowserWidget::tr("Load series") : + ctkDICOMVisualBrowserWidget::tr("Load %1 series").arg(numberOfSelectedSeries); QAction *loadAction = new QAction(loadString, seriesMenu); seriesMenu->addAction(loadAction); - QString metadataString = numberOfSelectedSeries == 1 ? tr("View series DICOM metadata") : - tr("View %1 series DICOM metadata").arg(numberOfSelectedSeries); + QString metadataString = numberOfSelectedSeries == 1 ? ctkDICOMVisualBrowserWidget::tr("View series DICOM metadata") : + ctkDICOMVisualBrowserWidget::tr("View %1 series DICOM metadata").arg(numberOfSelectedSeries); QAction *metadataAction = new QAction(metadataString, seriesMenu); seriesMenu->addAction(metadataAction); - QString forceRetrieveString = numberOfSelectedSeries == 1 ? tr("Force retrieve series") : - tr("Force retrieve for %1 series").arg(numberOfSelectedSeries); + QString forceRetrieveString = numberOfSelectedSeries == 1 ? ctkDICOMVisualBrowserWidget::tr("Force retrieve series") : + ctkDICOMVisualBrowserWidget::tr("Force retrieve for %1 series").arg(numberOfSelectedSeries); QAction *forceRetrieveAction = new QAction(forceRetrieveString, seriesMenu); seriesMenu->addAction(forceRetrieveAction); - QString deleteString = numberOfSelectedSeries == 1 ? tr("Delete series from local database") : - tr("Delete %1 series from local database").arg(numberOfSelectedSeries); + QString deleteString = numberOfSelectedSeries == 1 ? ctkDICOMVisualBrowserWidget::tr("Delete series from local database") : + ctkDICOMVisualBrowserWidget::tr("Delete %1 series from local database").arg(numberOfSelectedSeries); QAction *deleteAction = new QAction(deleteString, seriesMenu); seriesMenu->addAction(deleteAction); deleteAction->setVisible(this->isDeleteActionVisible()); - QString exportString = numberOfSelectedSeries == 1 ? tr("Export series to file system") : - tr("Export %1 series to file system").arg(numberOfSelectedSeries); + QString exportString = numberOfSelectedSeries == 1 ? ctkDICOMVisualBrowserWidget::tr("Export series to file system") : + ctkDICOMVisualBrowserWidget::tr("Export %1 series to file system").arg(numberOfSelectedSeries); QAction *exportAction = new QAction(exportString, seriesMenu); seriesMenu->addAction(exportAction); - QString sendString = numberOfSelectedSeries == 1 ? tr("Send series to DICOM server") : - tr("Send %1 series to DICOM server").arg(numberOfSelectedSeries); + QString sendString = numberOfSelectedSeries == 1 ? ctkDICOMVisualBrowserWidget::tr("Send series to DICOM server") : + ctkDICOMVisualBrowserWidget::tr("Send %1 series to DICOM server").arg(numberOfSelectedSeries); QAction* sendAction = new QAction(sendString, seriesMenu); sendAction->setVisible(this->isSendActionVisible()); seriesMenu->addAction(sendAction); @@ -3579,7 +3706,7 @@ void ctkDICOMVisualBrowserWidget::onPatientsTabMenuToolButtonClicked() } patientMenu->addSeparator(); - QString deleteString = tr("Delete all Patients from local database"); + QString deleteString = ctkDICOMVisualBrowserWidget::tr("Delete all Patients from local database"); QAction* deleteAction = new QAction(deleteString, patientMenu); deleteAction->setIcon(QIcon(":Icons/delete.svg")); patientMenu->addAction(deleteAction); @@ -3700,7 +3827,7 @@ void ctkDICOMVisualBrowserWidget::exportSeries(const QString& dirPath, const QSt if (!QDir().mkpath(destinationDir)) { //: %1 is the destination directory - QString errorString = tr("Unable to create export destination directory:\n\n%1" + QString errorString = ctkDICOMVisualBrowserWidget::tr("Unable to create export destination directory:\n\n%1" "\n\nHalting export.") .arg(destinationDir); ctkMessageBox createDirectoryErrorMessageBox(this); @@ -3714,13 +3841,14 @@ void ctkDICOMVisualBrowserWidget::exportSeries(const QString& dirPath, const QSt // show progress if (d->ExportProgress == 0) { - d->ExportProgress = new QProgressDialog(tr("DICOM Export"), tr("Close"), 0, 100, this, Qt::WindowTitleHint | Qt::WindowSystemMenuHint); + d->ExportProgress = new QProgressDialog(ctkDICOMVisualBrowserWidget::tr("DICOM Export"), + ctkDICOMVisualBrowserWidget::tr("Close"), 0, 100, this, Qt::WindowTitleHint | Qt::WindowSystemMenuHint); d->ExportProgress->setWindowModality(Qt::ApplicationModal); d->ExportProgress->setMinimumDuration(0); } QLabel* exportLabel = new QLabel( //: %1 is the series number - tr("Exporting series %1").arg(seriesNumber) + ctkDICOMVisualBrowserWidget::tr("Exporting series %1").arg(seriesNumber) ); d->ExportProgress->setLabel(exportLabel); d->ExportProgress->setValue(0); @@ -3737,9 +3865,9 @@ void ctkDICOMVisualBrowserWidget::exportSeries(const QString& dirPath, const QSt { d->ExportProgress->setValue(numFiles); //: %1 is the file path - QString errorString = tr("Export source file not found:\n\n%1" - "\n\nHalting export.\n\nError may be fixed via Repair.") - .arg(filePath); + QString errorString = ctkDICOMVisualBrowserWidget::tr("Export source file not found:\n\n%1" + "\n\nHalting export.\n\nError may be fixed via Repair.") + .arg(filePath); ctkMessageBox copyErrorMessageBox; copyErrorMessageBox.setText(errorString); copyErrorMessageBox.setIcon(QMessageBox::Warning); @@ -3750,9 +3878,9 @@ void ctkDICOMVisualBrowserWidget::exportSeries(const QString& dirPath, const QSt { d->ExportProgress->setValue(numFiles); //: %1 is the destination file name - QString errorString = tr("Export destination file already exists:\n\n%1" - "\n\nHalting export.") - .arg(destinationFileName); + QString errorString = ctkDICOMVisualBrowserWidget::tr("Export destination file already exists:\n\n%1" + "\n\nHalting export.") + .arg(destinationFileName); ctkMessageBox copyErrorMessageBox(this); copyErrorMessageBox.setText(errorString); copyErrorMessageBox.setIcon(QMessageBox::Warning); @@ -3765,10 +3893,10 @@ void ctkDICOMVisualBrowserWidget::exportSeries(const QString& dirPath, const QSt { d->ExportProgress->setValue(numFiles); //: %1 and %2 refers to source and destination file paths - QString errorString = tr("Failed to copy\n\n%1\n\nto\n\n%2" - "\n\nHalting export.") - .arg(filePath) - .arg(destinationFileName); + QString errorString = ctkDICOMVisualBrowserWidget::tr("Failed to copy\n\n%1\n\nto\n\n%2" + "\n\nHalting export.") + .arg(filePath) + .arg(destinationFileName); ctkMessageBox copyErrorMessageBox(this); copyErrorMessageBox.setText(errorString); copyErrorMessageBox.setIcon(QMessageBox::Warning); @@ -3819,7 +3947,7 @@ void ctkDICOMVisualBrowserWidget::onClose() return; } - this->onStop(); + this->onStop(true, true); this->close(); } @@ -3836,7 +3964,7 @@ void ctkDICOMVisualBrowserWidget::onLoad() } //------------------------------------------------------------------------------ -void ctkDICOMVisualBrowserWidget::onStop(bool stopPersistentTasks) +void ctkDICOMVisualBrowserWidget::onStop(bool stopPersistentTasks, bool removeJobs) { Q_D(ctkDICOMVisualBrowserWidget); if (!d->Scheduler) @@ -3845,7 +3973,7 @@ void ctkDICOMVisualBrowserWidget::onStop(bool stopPersistentTasks) } QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); - d->Scheduler->stopAllJobs(stopPersistentTasks); + d->Scheduler->stopAllJobs(stopPersistentTasks, removeJobs); d->updateFiltersWarnings(); d->ProgressFrame->hide(); QApplication::restoreOverrideCursor(); @@ -3854,7 +3982,7 @@ void ctkDICOMVisualBrowserWidget::onStop(bool stopPersistentTasks) //------------------------------------------------------------------------------ void ctkDICOMVisualBrowserWidget::closeEvent(QCloseEvent* event) { - this->onStop(); + this->onStop(true, true); event->accept(); } @@ -3903,8 +4031,8 @@ bool ctkDICOMVisualBrowserWidget::confirmDeleteSelectedUIDs(const QStringList& u } ctkMessageBox confirmDeleteDialog(this); - QString message = tr("Do you want to delete the following selected items from the LOCAL database? \n" - "The data will not be deleted from the PACs server. \n"); + QString message = ctkDICOMVisualBrowserWidget::tr("Do you want to delete the following selected items from the LOCAL database? \n" + "The data will not be deleted from the PACs server. \n"); // add the information about the selected UIDs int numUIDs = uids.size(); @@ -3938,8 +4066,8 @@ bool ctkDICOMVisualBrowserWidget::confirmDeleteSelectedUIDs(const QStringList& u confirmDeleteDialog.setText(message); confirmDeleteDialog.setIcon(QMessageBox::Question); - confirmDeleteDialog.addButton(tr("Delete"), QMessageBox::AcceptRole); - confirmDeleteDialog.addButton(tr("Cancel"), QMessageBox::RejectRole); + confirmDeleteDialog.addButton(ctkDICOMVisualBrowserWidget::tr("Delete"), QMessageBox::AcceptRole); + confirmDeleteDialog.addButton(ctkDICOMVisualBrowserWidget::tr("Cancel"), QMessageBox::RejectRole); confirmDeleteDialog.setDontShowAgainSettingsKey("VisualDICOMBrowser/DontConfirmDeleteSelected"); int response = confirmDeleteDialog.exec(); diff --git a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.h b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.h index 914e35daa0..a60566648d 100644 --- a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.h +++ b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.h @@ -27,6 +27,7 @@ // Qt includes #include #include +#include // ctkDICOMCore includes #include @@ -79,6 +80,10 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMVisualBrowserWidget : public QWidget Q_PROPERTY(QString databaseDirectoryBase READ databaseDirectoryBase WRITE setDatabaseDirectoryBase) Q_PROPERTY(QString filteringPatientID READ filteringPatientID WRITE setFilteringPatientID); Q_PROPERTY(QString filteringPatientName READ filteringPatientName WRITE setFilteringPatientName); + Q_PROPERTY(QString filteringStudyDescription READ filteringStudyDescription WRITE setFilteringStudyDescription); + Q_PROPERTY(ctkDICOMPatientItemWidget::DateType filteringDate READ filteringDate WRITE setFilteringDate); + Q_PROPERTY(QString filteringSeriesDescription READ filteringSeriesDescription WRITE setFilteringSeriesDescription); + Q_PROPERTY(QStringList filteringModalities READ filteringModalities WRITE setFilteringModalities); Q_PROPERTY(int numberOfOpenedStudiesPerPatient READ numberOfOpenedStudiesPerPatient WRITE setNumberOfOpenedStudiesPerPatient); Q_PROPERTY(ctkDICOMStudyItemWidget::ThumbnailSizeOption thumbnailSize READ thumbnailSize WRITE setThumbnailSize); Q_PROPERTY(ctkDICOMVisualBrowserWidget::ImportDirectoryMode ImportDirectoryMode READ importDirectoryMode WRITE setImportDirectoryMode) @@ -157,7 +162,7 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMVisualBrowserWidget : public QWidget Q_INVOKABLE int serversCount(); Q_INVOKABLE ctkDICOMServer* getNthServer(int id); Q_INVOKABLE ctkDICOMServer* getServer(const QString& connectionName); - Q_INVOKABLE void addServer(ctkDICOMServer* server); + Q_INVOKABLE int addServer(ctkDICOMServer* server); Q_INVOKABLE void removeServer(const QString& connectionName); Q_INVOKABLE void removeNthServer(int id); Q_INVOKABLE void removeAllServers(); @@ -249,10 +254,13 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMVisualBrowserWidget : public QWidget /// Add/Remove Patient item widget Q_INVOKABLE int addPatientItemWidget(const QString& patientItem); Q_INVOKABLE void removePatientItemWidget(const QString& patientItem); + Q_INVOKABLE ctkDICOMPatientItemWidget* getPatientItemWidgetByPatientItem(const QString& patientItem); + Q_INVOKABLE ctkDICOMPatientItemWidget* getPatientItemWidgetByPatientID(const QString& patientID); + Q_INVOKABLE ctkDICOMPatientItemWidget* getPatientItemWidgetByPatientName(const QString& patientName); ///@} - /// Get Patient item widget - Q_INVOKABLE ctkDICOMPatientItemWidget* getPatientItemWidgetByPatientName(const QString& patientName); + /// Get Patients tab widget + Q_INVOKABLE QTabWidget* patientsTabWidget(); ///@{ /// Accessors to status of last directory import operation @@ -376,7 +384,7 @@ public Q_SLOTS: ///@} /// stops all the operations - void onStop(bool stopPersistentTasks = false); + void onStop(bool stopPersistentTasks = false, bool removeJobs = false); ///@{ /// high level UI slots: close, load, warning