diff --git a/Libs/Core/ctkAbstractJob.cpp b/Libs/Core/ctkAbstractJob.cpp index fc9f923550..6a43b2789d 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; } //---------------------------------------------------------------------------- @@ -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..2eb3aa3288 100644 --- a/Libs/Core/ctkAbstractJob.h +++ b/Libs/Core/ctkAbstractJob.h @@ -56,6 +56,7 @@ class CTK_CORE_EXPORT ctkAbstractJob : public QObject Q_PROPERTY(QDateTime completionDateTime READ completionDateTime); Q_PROPERTY(QString runningThreadID READ runningThreadID WRITE setRunningThreadID); Q_PROPERTY(QString loggedText READ loggedText WRITE setLoggedText); + Q_PROPERTY(bool destroyAfterUse READ destroyAfterUse WRITE setDestroyAfterUse); public: explicit ctkAbstractJob(); @@ -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..6ad3cd63f4 100644 --- a/Libs/Core/ctkJobScheduler.cpp +++ b/Libs/Core/ctkJobScheduler.cpp @@ -198,10 +198,8 @@ bool ctkJobSchedulerPrivate::insertJob(QSharedPointer job) } //------------------------------------------------------------------------------ -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,16 +214,7 @@ 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(); @@ -234,7 +223,7 @@ bool ctkJobSchedulerPrivate::removeJob(const QString& jobUID) } //------------------------------------------------------------------------------ -void ctkJobSchedulerPrivate::removeJobs(const QStringList &jobUIDs) +void ctkJobSchedulerPrivate::cleanJobs(const QStringList &jobUIDs) { Q_Q(ctkJobScheduler); @@ -247,50 +236,73 @@ 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()); + job->freeUsedResources(); + } + } - 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")); + emit q->jobUserStopped(datas); +} - this->JobsConnections.remove(jobUID); - this->JobsQueue.remove(jobUID); +//------------------------------------------------------------------------------ +bool ctkJobSchedulerPrivate::removeJob(const QString& jobUID) +{ + 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); + QSharedPointer job = this->JobsQueue.value(jobUID); + if (!job || !this->JobsConnections.contains(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); } - emit q->jobUserStopped(datas); + emit this->queueJobsInThreadPool(); + + return true; } //------------------------------------------------------------------------------ -void ctkJobSchedulerPrivate::removeAllJobs() +void ctkJobSchedulerPrivate::removeJobs(const QStringList &jobUIDs) { Q_Q(ctkJobScheduler); + 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 (QSharedPointer job, this->JobsQueue) + + foreach (QString jobUID, jobUIDs) { - if (!job) + QSharedPointer job = this->JobsQueue.value(jobUID); + if (!job || !this->JobsConnections.contains(jobUID)) { continue; } - QString jobUID = job->jobUID(); - if (!this->JobsConnections.contains(jobUID)) - { - continue; - } + datas.append(job->toVariant()); QMap connections = this->JobsConnections.value(jobUID); QObject::disconnect(connections.value("started")); @@ -453,6 +465,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 +479,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 +566,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 +604,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 +625,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 +682,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 +702,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()); + emit d->queueJobsInThreadPool(); + return true; +} + +//---------------------------------------------------------------------------- +void ctkJobScheduler::retryJobs(const QStringList &jobUIDs) +{ + foreach (QString jobUID, jobUIDs) + { + this->retryJob(jobUID); + } +} + //---------------------------------------------------------------------------- int ctkJobScheduler::maximumThreadCount() const @@ -725,7 +801,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 +831,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 +861,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 +891,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..0d7efc8e77 100644 --- a/Libs/Core/ctkJobScheduler_p.h +++ b/Libs/Core/ctkJobScheduler_p.h @@ -58,9 +58,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..2fc7a1e8a1 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; diff --git a/Libs/DICOM/Core/ctkDICOMDatabase.cpp b/Libs/DICOM/Core/ctkDICOMDatabase.cpp index 725a69b066..73404ffffe 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(); @@ -2938,6 +2938,7 @@ void ctkDICOMDatabase::insert(QList> jobR if (jobType == ctkDICOMJobResponseSet::JobType::QueryPatients) { patientID = key; + jobResponseSet->setPatientID(patientID); } else if (jobType == ctkDICOMJobResponseSet::JobType::QueryStudies || jobType == ctkDICOMJobResponseSet::JobType::QuerySeries || @@ -2951,12 +2952,17 @@ void ctkDICOMDatabase::insert(QList> jobR dataset->SetElementAsString(DCM_PatientID, patientID); } + else + { + jobResponseSet->setPatientID(patientID); + } if (studyInstanceUID.isEmpty()) { if (jobType == ctkDICOMJobResponseSet::JobType::QueryStudies) { studyInstanceUID = key; + jobResponseSet->setStudyInstanceUID(studyInstanceUID); } else if (jobType == ctkDICOMJobResponseSet::JobType::QuerySeries || jobType == ctkDICOMJobResponseSet::JobType::QueryInstances || @@ -2969,6 +2975,10 @@ void ctkDICOMDatabase::insert(QList> jobR dataset->SetElementAsString(DCM_StudyInstanceUID, studyInstanceUID); } + else + { + jobResponseSet->setStudyInstanceUID(studyInstanceUID); + } if (patientName.isEmpty() && !studyInstanceUID.isEmpty()) { @@ -2982,6 +2992,7 @@ void ctkDICOMDatabase::insert(QList> jobR if (jobType == ctkDICOMJobResponseSet::JobType::QuerySeries) { seriesInstanceUID = key; + jobResponseSet->setSeriesInstanceUID(seriesInstanceUID); } else if (jobType == ctkDICOMJobResponseSet::JobType::QueryInstances || jobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries || @@ -2992,12 +3003,17 @@ void ctkDICOMDatabase::insert(QList> jobR dataset->SetElementAsString(DCM_SeriesInstanceUID, seriesInstanceUID); } + else + { + jobResponseSet->setSeriesInstanceUID(seriesInstanceUID); + } if (sopInstanceUID.isEmpty()) { if (jobType == ctkDICOMJobResponseSet::JobType::QueryInstances) { sopInstanceUID = key; + jobResponseSet->setSOPInstanceUID(sopInstanceUID); } else if (jobType == ctkDICOMJobResponseSet::JobType::RetrieveStudy || jobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries || @@ -3009,6 +3025,10 @@ void ctkDICOMDatabase::insert(QList> jobR dataset->SetElementAsString(DCM_SOPInstanceUID, sopInstanceUID); } + else + { + jobResponseSet->setSOPInstanceUID(sopInstanceUID); + } if (patientID.isEmpty()) { 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/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/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..61fe0dcf66 100644 --- a/Libs/DICOM/Core/ctkDICOMInserter.cpp +++ b/Libs/DICOM/Core/ctkDICOMInserter.cpp @@ -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..7bb405db65 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; 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/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/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..77aeeabcf4 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" @@ -147,6 +148,10 @@ bool ctkDICOMSchedulerPrivate::isServerAllowed(ctkDICOMServer *server, { return false; } + else if (server->trustedEnabled()) + { + return true; + } else if (allowedSeversForPatient.contains(server->connectionName())) { return true; @@ -172,6 +177,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 @@ -350,6 +394,7 @@ void ctkDICOMScheduler::retrieveSeries(const QString& patientID, foreach (QSharedPointer server, d->Servers) { + qDebug() << allowedSeversForPatient; if (!d->isServerAllowed(server.data(), allowedSeversForPatient)) { continue; @@ -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) diff --git a/Libs/DICOM/Core/ctkDICOMScheduler.h b/Libs/DICOM/Core/ctkDICOMScheduler.h index 3409f86a77..a23919add6 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::LowPriority); + ///@{ /// 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/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 94% rename from Libs/DICOM/Widgets/ctkDICOMThumbnailGenerator.cpp rename to Libs/DICOM/Core/ctkDICOMThumbnailGenerator.cpp index c57819d6c7..03cfd924c8 100644 --- a/Libs/DICOM/Widgets/ctkDICOMThumbnailGenerator.cpp +++ b/Libs/DICOM/Core/ctkDICOMThumbnailGenerator.cpp @@ -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..dcc0ad0637 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); + } } //---------------------------------------------------------------------------- @@ -1300,9 +1288,7 @@ void ctkDICOMJobListWidget::onStopButtonClicked() } } - QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); d->Scheduler->stopJobsByJobUIDs(jobsUIDsToStop); - QApplication::restoreOverrideCursor(); d->JobsView->clearSelection(); } @@ -1343,7 +1329,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 +1341,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..5b5385e774 100644 --- a/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.cpp @@ -102,6 +102,7 @@ class ctkDICOMPatientItemWidgetPrivate : public Ui_ctkDICOMPatientItemWidget QStringList AllowedServers; ctkDICOMPatientItemWidget::OperationStatus Status; + QString StoppedJobUID; bool IsGUIUpdating; bool QueryOn; @@ -135,6 +136,7 @@ ctkDICOMPatientItemWidgetPrivate::ctkDICOMPatientItemWidgetPrivate(ctkDICOMPatie this->AllowedServers = QStringList(); this->Status = ctkDICOMPatientItemWidget::NoOperation; + this->StoppedJobUID = ""; this->IsGUIUpdating = false; this->QueryOn = true; @@ -650,6 +652,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) @@ -903,7 +906,6 @@ void ctkDICOMPatientItemWidget::generateStudies(bool query, bool retrieve) //------------------------------------------------------------------------------ void ctkDICOMPatientItemWidget::generateSeriesAtToggle(bool toggled, const QString& studyItem) { - Q_D(ctkDICOMPatientItemWidget); if (!toggled || studyItem.isEmpty()) { return; @@ -986,6 +988,11 @@ void ctkDICOMPatientItemWidget::onJobUserStopped(QList datas) continue; } + if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryStudies) + { + d->StoppedJobUID = td.JobUID; + } + emit this->jobUserStopped(data); } } @@ -1004,6 +1011,11 @@ void ctkDICOMPatientItemWidget::onJobFailed(QList datas) continue; } + if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryStudies) + { + d->StoppedJobUID = td.JobUID; + } + emit this->jobFailed(data); } } diff --git a/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.h b/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.h index 7245240920..40d96fd593 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; @@ -205,6 +206,9 @@ 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 = ""); diff --git a/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp index ec3621de7f..836c779acc 100644 --- a/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp @@ -75,6 +75,7 @@ class ctkDICOMSeriesItemWidgetPrivate : public Ui_ctkDICOMSeriesItemWidget void createThumbnail(ctkDICOMJobDetail td); QImage drawModalityThumbnail(); void drawThumbnail(const QString& dicomFilePath, + const QString& patientID, const QString& studyInstanceUID, const QString& seriesInstanceUID, const QString& sopInstanceUID, @@ -107,6 +108,9 @@ class ctkDICOMSeriesItemWidgetPrivate : public Ui_ctkDICOMSeriesItemWidget QString Modality; QString ReferenceSeriesInserterJobUID; QString ReferenceInstanceInserterJobUID; + + QString StoppedJobUID; + bool StopJobs; bool RaiseJobsPriority; bool IsCloud; @@ -139,6 +143,8 @@ ctkDICOMSeriesItemWidgetPrivate::ctkDICOMSeriesItemWidgetPrivate(ctkDICOMSeriesI this->ReferenceSeriesInserterJobUID = ""; this->ReferenceInstanceInserterJobUID = ""; + this->StoppedJobUID = ""; + this->IsCloud = false; this->RetrieveFailed = false; this->RetrieveSeries = false; @@ -409,11 +415,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 +473,7 @@ QImage ctkDICOMSeriesItemWidgetPrivate::drawModalityThumbnail() //---------------------------------------------------------------------------- void ctkDICOMSeriesItemWidgetPrivate::drawThumbnail(const QString& dicomFilePath, + const QString& patientID, const QString& studyInstanceUID, const QString& seriesInstanceUID, const QString& sopInstanceUID, @@ -485,6 +494,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 +525,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 +534,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::HighPriority : QThread::LowPriority); + return; } if (!this->ThumbnailImage.load(thumbnailPath)) @@ -694,6 +704,7 @@ void ctkDICOMSeriesItemWidgetPrivate::updateThumbnailProgressBar() // change icons QString file = this->DicomDatabase->fileForInstance(this->CentralFrameSOPInstanceUID); this->drawThumbnail(file, + this->PatientID, this->StudyInstanceUID, this->SeriesInstanceUID, this->CentralFrameSOPInstanceUID, @@ -733,6 +744,7 @@ void ctkDICOMSeriesItemWidgetPrivate::updateRetrieveUIOnFailed() int numberOfFrames = instancesList.count(); this->drawThumbnail(file, + this->PatientID, this->StudyInstanceUID, this->SeriesInstanceUID, this->CentralFrameSOPInstanceUID, @@ -750,7 +762,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 +775,7 @@ void ctkDICOMSeriesItemWidgetPrivate::updateRetrieveUIOnFinished() } this->drawThumbnail(this->DicomDatabase->fileForInstance(this->CentralFrameSOPInstanceUID), + this->PatientID, this->StudyInstanceUID, this->SeriesInstanceUID, this->CentralFrameSOPInstanceUID, @@ -821,6 +834,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 +963,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 +1044,8 @@ void ctkDICOMSeriesItemWidget::onJobUserStopped(const QVariant &data) { d->updateRetrieveUIOnFailed(); } + + d->StoppedJobUID = td.JobUID; } } @@ -1056,6 +1073,8 @@ void ctkDICOMSeriesItemWidget::onJobFailed(const QVariant &data) { d->updateRetrieveUIOnFailed(); } + + d->StoppedJobUID = td.JobUID; } } @@ -1137,12 +1156,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..776e6fe7fd 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,6 +58,7 @@ 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; @@ -179,6 +181,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..f1dc89f9a1 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())); } //---------------------------------------------------------------------------- @@ -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() { @@ -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..ba1cb5be3f 100644 --- a/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp @@ -78,8 +78,6 @@ class ctkDICOMStudyItemWidgetPrivate : public Ui_ctkDICOMStudyItemWidget QString FilteringSeriesDescription; QStringList FilteringModalities; - QStringList AllowedServers; - ctkDICOMStudyItemWidget::OperationStatus Status; QSharedPointer DicomDatabase; QSharedPointer Scheduler; @@ -93,6 +91,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 +117,7 @@ ctkDICOMStudyItemWidgetPrivate::ctkDICOMStudyItemWidgetPrivate(ctkDICOMStudyItem this->AllowedServers = QStringList(); this->Status = ctkDICOMStudyItemWidget::NoOperation; + this->StoppedJobUID = ""; this->DicomDatabase = nullptr; this->Scheduler = nullptr; @@ -483,7 +486,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) @@ -858,6 +861,8 @@ void ctkDICOMStudyItemWidget::onJobUserStopped(const QVariant &data) d->Status = ctkDICOMStudyItemWidget::Failed; d->OperationStatusPushButton->setIcon(QIcon(":/Icons/error_red.svg")); + + d->StoppedJobUID = td.JobUID; } //------------------------------------------------------------------------------ @@ -881,6 +886,8 @@ void ctkDICOMStudyItemWidget::onJobFailed(const QVariant &data) d->Status = ctkDICOMStudyItemWidget::Failed; d->OperationStatusPushButton->setIcon(QIcon(":/Icons/error_red.svg")); + + d->StoppedJobUID = td.JobUID; } //------------------------------------------------------------------------------ @@ -925,12 +932,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..9bfc8412f2 100644 --- a/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.h +++ b/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.h @@ -64,6 +64,7 @@ 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; @@ -157,21 +158,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 @@ -211,6 +197,24 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMStudyItemWidget : public QWidget /// 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&); diff --git a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp index c720752def..f3a150108a 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); @@ -2182,6 +2182,13 @@ ctkDICOMPatientItemWidget* ctkDICOMVisualBrowserWidget::getPatientItemWidgetByPa return nullptr; } +//------------------------------------------------------------------------------ +QTabWidget* ctkDICOMVisualBrowserWidget::patientsTabWidget() +{ + Q_D(ctkDICOMVisualBrowserWidget); + return d->PatientsTabWidget; +} + //------------------------------------------------------------------------------ int ctkDICOMVisualBrowserWidget::patientsAddedDuringImport() { @@ -3222,12 +3229,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())); + } } } @@ -3819,7 +3825,7 @@ void ctkDICOMVisualBrowserWidget::onClose() return; } - this->onStop(); + this->onStop(true, true); this->close(); } @@ -3836,7 +3842,7 @@ void ctkDICOMVisualBrowserWidget::onLoad() } //------------------------------------------------------------------------------ -void ctkDICOMVisualBrowserWidget::onStop(bool stopPersistentTasks) +void ctkDICOMVisualBrowserWidget::onStop(bool stopPersistentTasks, bool removeJobs) { Q_D(ctkDICOMVisualBrowserWidget); if (!d->Scheduler) @@ -3845,7 +3851,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 +3860,7 @@ void ctkDICOMVisualBrowserWidget::onStop(bool stopPersistentTasks) //------------------------------------------------------------------------------ void ctkDICOMVisualBrowserWidget::closeEvent(QCloseEvent* event) { - this->onStop(); + this->onStop(true, true); event->accept(); } diff --git a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.h b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.h index 914e35daa0..659bc18b9b 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(); @@ -254,6 +259,9 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMVisualBrowserWidget : public QWidget /// 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 int patientsAddedDuringImport(); @@ -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