diff --git a/Libs/Core/ctkJobScheduler.cpp b/Libs/Core/ctkJobScheduler.cpp index 49017321ad..e6ba99488f 100644 --- a/Libs/Core/ctkJobScheduler.cpp +++ b/Libs/Core/ctkJobScheduler.cpp @@ -70,12 +70,17 @@ void ctkJobSchedulerPrivate::init() void ctkJobSchedulerPrivate::onQueueJobsInThreadPool() { Q_Q(ctkJobScheduler); + + if (this->FreezeJobsScheduling) { - QMutexLocker locker(&this->QueueMutex); - if (this->FreezeJobsScheduling) - { return; - } + } + + { + // 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 (QThread::Priority priority, (QList() << QThread::Priority::HighestPriority @@ -86,6 +91,11 @@ void ctkJobSchedulerPrivate::onQueueJobsInThreadPool() { foreach (QSharedPointer job, this->JobsQueue) { + if (this->FreezeJobsScheduling) + { + return; + } + if (job->priority() != priority) { continue; @@ -130,6 +140,16 @@ bool ctkJobSchedulerPrivate::insertJob(QSharedPointer job) return false; } + if (this->FreezeJobsScheduling) + { + logger.debug(QString("ctkJobScheduler: job object %1 of type %2 in thread %3 " + "not added to the job list since jobs are being stopped.\n") + .arg(job->jobUID()) + .arg(job->className()) + .arg(QString::number(reinterpret_cast(QThread::currentThreadId())), 16)); + return false; + } + logger.debug(QString("ctkJobScheduler: creating job object %1 of type %2 in thread %3.\n") .arg(job->jobUID()) .arg(job->className()) @@ -165,23 +185,14 @@ bool ctkJobSchedulerPrivate::insertJob(QSharedPointer job) }; { + // 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); this->JobsQueue.insert(job->jobUID(), job); this->JobsConnections.insert(job->jobUID(), connections); } emit q->jobInitialized(job->toVariant()); - - if (this->FreezeJobsScheduling) - { - logger.debug(QString("ctkJobScheduler: job object %1 of type %2 in thread %3 " - "not added to the list since jobs are being stopped.\n") - .arg(job->jobUID()) - .arg(job->className()) - .arg(QString::number(reinterpret_cast(QThread::currentThreadId())), 16)); - return false; - } - emit this->queueJobsInThreadPool(); return true; } @@ -189,11 +200,15 @@ bool ctkJobSchedulerPrivate::insertJob(QSharedPointer job) //------------------------------------------------------------------------------ bool ctkJobSchedulerPrivate::removeJob(const QString& jobUID) { + Q_Q(ctkJobScheduler); + logger.debug(QString("ctkJobScheduler: deleting job object %1 in thread %2.\n") .arg(jobUID) .arg(QString::number(reinterpret_cast(QThread::currentThreadId()), 16))); { + // The QMutexLocker is enclosed within brackets to restrict its scope and + // prevent conflicts with other QMutexLockers within the scheduler's methods. QMutexLocker locker(&this->QueueMutex); QSharedPointer job = this->JobsQueue.value(jobUID); if (!job || !this->JobsConnections.contains(jobUID)) @@ -214,6 +229,7 @@ bool ctkJobSchedulerPrivate::removeJob(const QString& jobUID) } emit this->queueJobsInThreadPool(); + return true; } @@ -257,6 +273,8 @@ void ctkJobSchedulerPrivate::removeJobs(const QStringList &jobUIDs) //------------------------------------------------------------------------------ void ctkJobSchedulerPrivate::removeAllJobs() { + Q_Q(ctkJobScheduler); + { // The QMutexLocker is enclosed within brackets to restrict its scope and // prevent conflicts with other QMutexLockers within the scheduler's methods. @@ -558,38 +576,10 @@ void ctkJobScheduler::stopAllJobs(bool stopPersistentJobs) // In addition, to speedup the cleaning of jobs, we remove them with one call removeJobs, // instead of using the signal QMap connections = d->JobsConnections.value(jobUID); - //QObject::disconnect(connections.value("userStopped")); + QObject::disconnect(connections.value("userStopped")); job->setStatus(ctkAbstractJob::JobStatus::UserStopped); initializedStoppedJobsUIDs.append(jobUID); } - - // Try to stop jobs with a worker, but still not running. - // (in queue in the QThreadPool, still in the main thread) - foreach (QSharedPointer worker, d->Workers) - { - QSharedPointer job = worker->jobShared(); - if (!job) - { - continue; - } - - QString jobUID = job->jobUID(); - if (!d->JobsConnections.contains(jobUID)) - { - continue; - } - - // trytake stops workers not already running - // these corresponds to jobs with status Queued - if (d->ThreadPool->tryTake(worker.data())) - { - this->deleteWorker(job->jobUID()); - QMap connections = d->JobsConnections.value(jobUID); - //QObject::disconnect(connections.value("userStopped")); - job->setStatus(ctkAbstractJob::JobStatus::UserStopped); - initializedStoppedJobsUIDs.append(job->jobUID()); - } - } } d->removeJobs(initializedStoppedJobsUIDs); @@ -650,39 +640,10 @@ void ctkJobScheduler::stopJobsByJobUIDs(const QStringList &jobUIDs) // In addition, to speedup the cleaning of jobs, we remove them with one call removeJobs, // instead of using the signal QMap connections = d->JobsConnections.value(jobUID); - //QObject::disconnect(connections.value("userStopped")); + QObject::disconnect(connections.value("userStopped")); job->setStatus(ctkAbstractJob::JobStatus::UserStopped); initializedStoppedJobsUIDs.append(job->jobUID()); } - - // Try to stop jobs with a worker, but still not running. - // (in queue in the QThreadPool, still in the main thread) - foreach (QSharedPointer worker, d->Workers) - { - QSharedPointer job = worker->jobShared(); - if (!job) - { - continue; - } - - QString jobUID = job->jobUID(); - if (jobUID.isEmpty() || !jobUIDs.contains(jobUID) || - !d->JobsConnections.contains(jobUID)) - { - continue; - } - - // trytake stops workers not already running, - // these corresponds to jobs with status Queued - if (d->ThreadPool->tryTake(worker.data())) - { - this->deleteWorker(job->jobUID()); - QMap connections = d->JobsConnections.value(jobUID); - //QObject::disconnect(connections.value("userStopped")); - job->setStatus(ctkAbstractJob::JobStatus::UserStopped); - initializedStoppedJobsUIDs.append(job->jobUID()); - } - } } d->removeJobs(initializedStoppedJobsUIDs); diff --git a/Libs/DICOM/Core/ctkDICOMAbstractThumbnailGenerator.h b/Libs/DICOM/Core/ctkDICOMAbstractThumbnailGenerator.h index dc41855416..ffb253aa07 100644 --- a/Libs/DICOM/Core/ctkDICOMAbstractThumbnailGenerator.h +++ b/Libs/DICOM/Core/ctkDICOMAbstractThumbnailGenerator.h @@ -43,7 +43,10 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMAbstractThumbnailGenerator : public QObject explicit ctkDICOMAbstractThumbnailGenerator(QObject* parent = 0); virtual ~ctkDICOMAbstractThumbnailGenerator(); - virtual bool generateThumbnail(DicomImage* dcmImage, const QString& path ) = 0; + virtual bool generateThumbnail(DicomImage* dcmImage, const QString& path, + QVector color = QVector{169, 169, 169}) = 0; + virtual void generateDocumentThumbnail(const QString &thumbnailPath, + QVector color = QVector{169, 169, 169}) = 0; protected: QScopedPointer d_ptr; diff --git a/Libs/DICOM/Core/ctkDICOMDatabase.cpp b/Libs/DICOM/Core/ctkDICOMDatabase.cpp index 8bcc66bcbf..d40b0e3487 100644 --- a/Libs/DICOM/Core/ctkDICOMDatabase.cpp +++ b/Libs/DICOM/Core/ctkDICOMDatabase.cpp @@ -856,34 +856,6 @@ bool ctkDICOMDatabasePrivate::insertPatientStudySeries(const ctkDICOMItem& datas return databaseWasChanged; } -//------------------------------------------------------------------------------ -bool ctkDICOMDatabasePrivate::storeThumbnailFile(const QString& originalFilePath, - const QString& studyInstanceUID, const QString& seriesInstanceUID, const QString& sopInstanceUID) -{ - Q_Q(ctkDICOMDatabase); - if (!this->ThumbnailGenerator) - { - return false; - } - // Create thumbnail here - QString thumbnailPath = q->databaseDirectory() + - "/thumbs/" + this->internalStoragePath(studyInstanceUID, seriesInstanceUID, sopInstanceUID) + ".png"; - QFileInfo thumbnailInfo(thumbnailPath); - if (thumbnailInfo.exists() && (thumbnailInfo.lastModified() > QFileInfo(originalFilePath).lastModified())) - { - // thumbnail already exists and up-to-date - return true; - } - QDir destinationDir(thumbnailInfo.dir()); - if (!destinationDir.exists()) - { - destinationDir.mkpath("."); - } - DicomImage dcmImage(QDir::toNativeSeparators(originalFilePath).toUtf8()); - return this->ThumbnailGenerator->generateThumbnail(&dcmImage, thumbnailPath); -} - - //------------------------------------------------------------------------------ bool ctkDICOMDatabasePrivate::uidsForDataSet(const ctkDICOMItem& dataset, QString& patientsName, QString& patientID, QString& studyInstanceUID, QString& seriesInstanceUID) @@ -1036,7 +1008,7 @@ void ctkDICOMDatabasePrivate::insert(const ctkDICOMItem& dataset, const QString& } if (generateThumbnail) { - this->storeThumbnailFile(storedFilePath, studyInstanceUID, seriesInstanceUID, sopInstanceUID); + q->storeThumbnailFile(storedFilePath, studyInstanceUID, seriesInstanceUID, sopInstanceUID); } } if (q->isInMemory() && databaseWasChanged) @@ -2375,6 +2347,73 @@ QDateTime ctkDICOMDatabase::insertDateTimeForInstance(QString sopInstanceUID) return result; } +//------------------------------------------------------------------------------ +QString ctkDICOMDatabase::thumbnailPathForInstance(const QString &studyInstanceUID, + const QString &seriesInstanceUID, + const QString &sopInstanceUID) +{ + Q_D(ctkDICOMDatabase); + QString thumbnailPath = this->databaseDirectory() + + "/thumbs/" + d->internalStoragePath(studyInstanceUID, seriesInstanceUID, sopInstanceUID) + ".png"; + + QFileInfo thumbnailInfo(thumbnailPath); + if (thumbnailInfo.exists()) + { + // thumbnail exists + return thumbnailPath; + } + else + { + return ""; + } +} + +//------------------------------------------------------------------------------ +bool ctkDICOMDatabase::storeThumbnailFile(const QString &originalFilePath, + const QString &studyInstanceUID, + const QString &seriesInstanceUID, + const QString &sopInstanceUID, + const QString& modality, + QVector color) +{ + Q_D(ctkDICOMDatabase); + if (!d->ThumbnailGenerator) + { + return false; + } + + QString thumbnailPath = this->databaseDirectory() + + "/thumbs/" + d->internalStoragePath(studyInstanceUID, seriesInstanceUID, sopInstanceUID) + ".png"; + + QFileInfo thumbnailInfo(thumbnailPath); + if (thumbnailInfo.exists() && (thumbnailInfo.lastModified() > QFileInfo(originalFilePath).lastModified())) + { + // thumbnail already exists and it is up-to-date + return true; + } + + QDir destinationDir(thumbnailInfo.dir()); + if (!destinationDir.exists()) + { + destinationDir.mkpath("."); + } + + if (modality == "SEG") + { + // 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. Therefore, instead of rendering the image, + // the thumbnail will be generated as a solid color box and the SEG modality will be displayed. + d->ThumbnailGenerator->generateDocumentThumbnail(thumbnailPath, color); + return true; + } + else + { + DicomImage dcmImage(QDir::toNativeSeparators(originalFilePath).toUtf8()); + return d->ThumbnailGenerator->generateThumbnail(&dcmImage, thumbnailPath, color); + } +} + //------------------------------------------------------------------------------ int ctkDICOMDatabase::patientsCount() { @@ -2848,7 +2887,7 @@ void ctkDICOMDatabase::insert(const QList& ind if (generateThumbnail) { - d->storeThumbnailFile(storedFilePath, studyInstanceUID, seriesInstanceUID, sopInstanceUID); + this->storeThumbnailFile(storedFilePath, studyInstanceUID, seriesInstanceUID, sopInstanceUID); } } } @@ -3116,7 +3155,7 @@ void ctkDICOMDatabase::insert(QList> jobR } if (generateThumbnail) { - d->storeThumbnailFile(storedFilePath, studyInstanceUID, seriesInstanceUID, sopInstanceUID); + this->storeThumbnailFile(storedFilePath, studyInstanceUID, seriesInstanceUID, sopInstanceUID); } } } diff --git a/Libs/DICOM/Core/ctkDICOMDatabase.h b/Libs/DICOM/Core/ctkDICOMDatabase.h index cc680f156c..d074967f3d 100644 --- a/Libs/DICOM/Core/ctkDICOMDatabase.h +++ b/Libs/DICOM/Core/ctkDICOMDatabase.h @@ -208,6 +208,15 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMDatabase : public QObject Q_INVOKABLE QString seriesForFile(QString fileName); Q_INVOKABLE QString instanceForFile(const QString fileName); Q_INVOKABLE QDateTime insertDateTimeForInstance(const QString fileName); + Q_INVOKABLE QString thumbnailPathForInstance(const QString& studyInstanceUID, + const QString& seriesInstanceUID, + const QString& sopInstanceUID); + Q_INVOKABLE bool storeThumbnailFile(const QString& originalFilePath, + const QString& studyInstanceUID, + const QString& seriesInstanceUID, + const QString& sopInstanceUID, + const QString& modality = "", + QVector color = QVector{169, 169, 169}); Q_INVOKABLE int patientsCount(); Q_INVOKABLE int studiesCount(); @@ -263,14 +272,13 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMDatabase : public QObject /// does only make sense if a full object is received. /// @param @generateThumbnail If true, a thumbnail is generated. /// - Q_INVOKABLE void insert( const ctkDICOMItem& ctkDataset, - bool storeFile, bool generateThumbnail); - void insert ( DcmItem *item, - bool storeFile = true, bool generateThumbnail = true); - Q_INVOKABLE void insert ( const QString& filePath, - bool storeFile = true, bool generateThumbnail = true, - bool createHierarchy = true, - const QString& destinationDirectoryName = QString() ); + Q_INVOKABLE void insert(const ctkDICOMItem& ctkDataset, + bool storeFile, bool generateThumbnail); + void insert (DcmItem *item, bool storeFile = true, bool generateThumbnail = true); + Q_INVOKABLE void insert (const QString& filePath, + bool storeFile = true, bool generateThumbnail = true, + bool createHierarchy = true, + const QString& destinationDirectoryName = QString()); Q_INVOKABLE void insert(const QList& indexingResults); Q_INVOKABLE void insert(QList> jobResponseSets); diff --git a/Libs/DICOM/Core/ctkDICOMDatabase_p.h b/Libs/DICOM/Core/ctkDICOMDatabase_p.h index 1efbf2b31c..87ad90bbb5 100644 --- a/Libs/DICOM/Core/ctkDICOMDatabase_p.h +++ b/Libs/DICOM/Core/ctkDICOMDatabase_p.h @@ -72,10 +72,6 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMDatabasePrivate /// Returns false in case of an error bool indexingStatusForFile(const QString& filePath, const QString& sopInstanceUID, bool& datasetInDatabase, bool& datasetUpToDate, QString& databaseFilename); - /// Retrieve thumbnail from file and store in database folder. - bool storeThumbnailFile(const QString& originalFilePath, - const QString& studyInstanceUID, const QString& seriesInstanceUID, const QString& sopInstanceUID); - /// Get basic UIDs for a data set, return true if the data set has all the required tags bool uidsForDataSet(const ctkDICOMItem& dataset, QString& patientsName, QString& patientID, QString& studyInstanceUID, QString& seriesInstanceUID); bool uidsForDataSet(QString& patientsName, QString& patientID, QString& studyInstanceUID); diff --git a/Libs/DICOM/Core/ctkDICOMScheduler.cpp b/Libs/DICOM/Core/ctkDICOMScheduler.cpp index 38674c8d26..95026d33a3 100644 --- a/Libs/DICOM/Core/ctkDICOMScheduler.cpp +++ b/Libs/DICOM/Core/ctkDICOMScheduler.cpp @@ -751,7 +751,7 @@ void ctkDICOMScheduler::waitForFinishByDICOMUIDs(const QStringList& patientIDs, bool wait = true; while (wait) { - QCoreApplication::processEvents(); + qApp->processEvents(QEventLoop::ExcludeUserInputEvents); d->ThreadPool->waitForDone(300); wait = false; @@ -833,7 +833,7 @@ QList> ctkDICOMScheduler::getJobsByDICOMUIDs(cons ctkDICOMJob* dicomJob = qobject_cast(job.data()); if (!dicomJob) { - qCritical() << Q_FUNC_INFO << " failed: unexpected type of job"; + logger.debug("ctkDICOMScheduler::getJobsByDICOMUIDs: unexpected type of job."); continue; } @@ -875,7 +875,7 @@ void ctkDICOMScheduler::stopJobsByDICOMUIDs(const QStringList& patientIDs, if (numberOfInputLists == 0) { - logger.warn("ctkDICOMScheduler::stopJobsByDICOMUIDs failed: all the provided lists with UIDs are empty."); + logger.debug("ctkDICOMScheduler::stopJobsByDICOMUIDs: all the provided lists with UIDs are empty."); return; } @@ -895,7 +895,7 @@ void ctkDICOMScheduler::stopJobsByDICOMUIDs(const QStringList& patientIDs, ctkDICOMJob* dicomJob = qobject_cast(job.data()); if (!dicomJob) { - qCritical() << Q_FUNC_INFO << " failed: unexpected type of job"; + logger.debug("ctkDICOMScheduler::stopJobsByDICOMUIDs: unexpected type of job."); continue; } @@ -1017,7 +1017,7 @@ void ctkDICOMScheduler::raiseJobsPriorityForSeries(const QStringList& selectedSe ctkDICOMJob* dicomJob = qobject_cast(job.data()); if (!dicomJob) { - qCritical() << Q_FUNC_INFO << " failed: unexpected type of job"; + logger.debug("ctkDICOMScheduler::raiseJobsPriorityForSeries: unexpected type of job."); continue; } diff --git a/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp index 66e97c9cd8..83a28a343c 100644 --- a/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp @@ -182,6 +182,10 @@ QString QCenteredItemModel::getJobTypeAsString(QString jobClass, ctkDICOMJob::DI { return QCenteredItemModel::tr("Echo server"); } + else if (jobClass == "ctkDICOMInserterJob") + { + return QCenteredItemModel::tr("Inserter"); + } return QCenteredItemModel::tr(""); } @@ -195,11 +199,6 @@ void QCenteredItemModel::addJob(const ctkDICOMJobDetail &td, return; } - if (td.JobClass == "ctkDICOMInserterJob") - { - return; - } - int row = 0; // add the job to the top this->insertRow(row); @@ -279,11 +278,6 @@ void QCenteredItemModel::addJob(const ctkDICOMJobDetail &td, //---------------------------------------------------------------------------- void QCenteredItemModel::updateJobStatus(const ctkDICOMJobDetail &td, const JobStatus &status) { - if (td.JobClass == "ctkDICOMInserterJob") - { - return; - } - QList list = this->findItems(td.JobUID, Qt::MatchExactly, Columns::JobUID); if (list.empty()) { diff --git a/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.cpp index 4cc33d6c64..55a9eafc7f 100644 --- a/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.cpp @@ -891,7 +891,8 @@ void ctkDICOMPatientItemWidget::generateStudies(bool query, bool retrieve) d->QueryOn = query; d->RetrieveOn = retrieve; d->createStudies(); - if (query && d->Scheduler && d->Scheduler->queryRetrieveServersCount() > 0) + if (query && d->Scheduler && + d->Scheduler->queryRetrieveServersCount() > 0) { d->Scheduler->queryStudies(d->PatientID, QThread::NormalPriority, diff --git a/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp index b39be6a2bf..c9426ec2f3 100644 --- a/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp @@ -73,8 +73,13 @@ class ctkDICOMSeriesItemWidgetPrivate : public Ui_ctkDICOMSeriesItemWidget void disconnectFromTop(); QString getDICOMCenterFrameFromInstances(QStringList instancesList); void createThumbnail(ctkDICOMJobDetail td); - void drawModalityThumbnail(); - void drawThumbnail(const QString& file, int numberOfFrames); + QImage drawModalityThumbnail(); + void drawThumbnail(const QString& dicomFilePath, + const QString& studyInstanceUID, + const QString& seriesInstanceUID, + const QString& sopInstanceUID, + const QString& modality, + int numberOfFrames); void drawTextWithShadow(QPainter *painter, const QFont &font, int x, @@ -112,7 +117,6 @@ class ctkDICOMSeriesItemWidgetPrivate : public Ui_ctkDICOMSeriesItemWidget int ThumbnailSizePixel; int NumberOfDownloads; QImage ThumbnailImage; - bool isThumbnailDocument; bool QueryOn; bool RetrieveOn; @@ -142,7 +146,6 @@ ctkDICOMSeriesItemWidgetPrivate::ctkDICOMSeriesItemWidgetPrivate(ctkDICOMSeriesI this->IsVisible = false; this->StopJobs = false; this->RaiseJobsPriority = false; - this->isThumbnailDocument = false; this->ThumbnailSizePixel = 200; this->NumberOfDownloads = 0; @@ -383,7 +386,7 @@ void ctkDICOMSeriesItemWidgetPrivate::createThumbnail(ctkDICOMJobDetail td) this->StudyInstanceUID, this->SeriesInstanceUID, this->CentralFrameSOPInstanceUID, - this->RaiseJobsPriority ? QThread::HighestPriority : QThread::HighPriority, + this->RaiseJobsPriority ? QThread::HighPriority : QThread::HighPriority, this->AllowedServers); this->RetrieveSeries = true; return; @@ -399,36 +402,35 @@ void ctkDICOMSeriesItemWidgetPrivate::createThumbnail(ctkDICOMJobDetail td) this->Scheduler->retrieveSeries(this->PatientID, this->StudyInstanceUID, this->SeriesInstanceUID, - this->RaiseJobsPriority ? QThread::HighestPriority : QThread::LowPriority, + this->RaiseJobsPriority ? QThread::HighPriority : QThread::LowPriority, this->AllowedServers); } } - file = this->DicomDatabase->fileForInstance(this->CentralFrameSOPInstanceUID); - if (file.isEmpty()) - { - return; - } - if (jobSopInstanceUID.isEmpty() || ((jobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance || jobType == ctkDICOMJobResponseSet::JobType::StoreSOPInstance) && jobSopInstanceUID == this->CentralFrameSOPInstanceUID) || renderThumbnail) { - this->drawThumbnail(file, numberOfFrames); + this->drawThumbnail(this->DicomDatabase->fileForInstance(this->CentralFrameSOPInstanceUID), + this->StudyInstanceUID, + this->SeriesInstanceUID, + this->CentralFrameSOPInstanceUID, + this->Modality, + numberOfFrames); } } //---------------------------------------------------------------------------- -void ctkDICOMSeriesItemWidgetPrivate::drawModalityThumbnail() +QImage ctkDICOMSeriesItemWidgetPrivate::drawModalityThumbnail() { Q_Q(ctkDICOMSeriesItemWidget); if (!this->DicomDatabase) { - logger.error("drawThumbnail failed, no DICOM Database has been set. \n"); - return; + logger.error("drawModalityThumbnail failed, no DICOM Database has been set. \n"); + return QImage(); } qreal scalingFactor = q->devicePixelRatioF(); @@ -461,10 +463,16 @@ void ctkDICOMSeriesItemWidgetPrivate::drawModalityThumbnail() } this->SeriesThumbnail->setPixmap(resultPixmap); + return resultPixmap.toImage(); } //---------------------------------------------------------------------------- -void ctkDICOMSeriesItemWidgetPrivate::drawThumbnail(const QString& file, int numberOfFrames) +void ctkDICOMSeriesItemWidgetPrivate::drawThumbnail(const QString& dicomFilePath, + const QString& studyInstanceUID, + const QString& seriesInstanceUID, + const QString& sopInstanceUID, + const QString& modality, + int numberOfFrames) { Q_Q(ctkDICOMSeriesItemWidget); @@ -474,6 +482,36 @@ void ctkDICOMSeriesItemWidgetPrivate::drawThumbnail(const QString& file, int num return; } + if (dicomFilePath.isEmpty()) + { + logger.error("drawThumbnail failed, dicomFilePath is empty. \n"); + return; + } + + if (studyInstanceUID.isEmpty()) + { + logger.error("drawThumbnail failed, studyInstanceUID is empty. \n"); + return; + } + + if (seriesInstanceUID.isEmpty()) + { + logger.error("drawThumbnail failed, seriesInstanceUID is empty. \n"); + return; + } + + if (sopInstanceUID.isEmpty()) + { + logger.error("drawThumbnail failed, sopInstanceUID is empty. \n"); + return; + } + + if (modality.isEmpty()) + { + logger.error("drawThumbnail failed, modality is empty. \n"); + return; + } + qreal scalingFactor = q->devicePixelRatioF(); int scaledThumbnailSizePixel = this->ThumbnailSizePixel * scalingFactor; int margin = floor(scaledThumbnailSizePixel / 50.); @@ -482,102 +520,93 @@ void ctkDICOMSeriesItemWidgetPrivate::drawThumbnail(const QString& file, int num QFont font = this->SeriesThumbnail->font(); font.setBold(true); font.setPixelSize(textSize); - QPixmap resultPixmap(scaledThumbnailSizePixel, scaledThumbnailSizePixel); - ctkDICOMThumbnailGenerator thumbnailGenerator; - thumbnailGenerator.setWidth(scaledThumbnailSizePixel); - thumbnailGenerator.setHeight(scaledThumbnailSizePixel); - bool thumbnailGenerated = true; - bool emptyThumbnailGenerated = false; - QPainter painter; - if (this->ThumbnailImage.isNull()) { - if (!thumbnailGenerator.generateThumbnail(file, this->ThumbnailImage)) + ctkDICOMThumbnailGenerator thumbnailGenerator; + thumbnailGenerator.setWidth(scaledThumbnailSizePixel); + thumbnailGenerator.setHeight(scaledThumbnailSizePixel); + + QString thumbnailPath = this->DicomDatabase->thumbnailPathForInstance(studyInstanceUID, seriesInstanceUID, sopInstanceUID); + if (thumbnailPath.isEmpty()) { - thumbnailGenerated = false; - emptyThumbnailGenerated = true; - this->isThumbnailDocument = true; QColor backgroundColor = this->SeriesThumbnail->palette().color(QPalette::Normal, QPalette::Window); - thumbnailGenerator.generateBlankThumbnail(this->ThumbnailImage, backgroundColor); - resultPixmap = QPixmap::fromImage(this->ThumbnailImage); - if (painter.begin(&resultPixmap)) - { - painter.setRenderHint(QPainter::Antialiasing); - QSvgRenderer renderer(QString(":Icons/text_document.svg")); - renderer.render(&painter); - painter.end(); - } + QVector colorVector = {backgroundColor.red(), backgroundColor.green(), backgroundColor.blue()}; + this->DicomDatabase->storeThumbnailFile(dicomFilePath, studyInstanceUID, seriesInstanceUID, + sopInstanceUID, modality, colorVector); + thumbnailPath = this->DicomDatabase->thumbnailPathForInstance(studyInstanceUID, seriesInstanceUID, sopInstanceUID); } - } - if (thumbnailGenerated) - { - QColor firstPixelColor = this->ThumbnailImage.pixelColor(0, 0); - resultPixmap.fill(firstPixelColor); + if (!this->ThumbnailImage.load(thumbnailPath)) + { + logger.error("drawThumbnail failed, could not load png file. \n"); + return; + } } - if (thumbnailGenerated && !this->isThumbnailDocument) + QColor firstPixelColor = this->ThumbnailImage.pixelColor(0, 0); + resultPixmap.fill(firstPixelColor); + + QPainter painter; + if (painter.begin(&resultPixmap)) { - if (painter.begin(&resultPixmap)) + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setRenderHint(QPainter::SmoothPixmapTransform, true); + painter.setFont(font); + + QRectF rect = resultPixmap.rect(); + int x = int((rect.width() * 0.5) - (this->ThumbnailImage.rect().width() * 0.5)); + int y = int((rect.height() * 0.5) - (this->ThumbnailImage.rect().height() * 0.5)); + + QPixmap thumbnailPixmap = QPixmap::fromImage(this->ThumbnailImage); + painter.drawPixmap(x, y, thumbnailPixmap); + + QString topLeftString = ctkDICOMSeriesItemWidget::tr("Series: %1\n%2").arg(this->SeriesNumber).arg(this->Modality); + if (modality == "SEG") { - painter.setRenderHint(QPainter::Antialiasing, true); - painter.setRenderHint(QPainter::SmoothPixmapTransform, true); - painter.setFont(font); - - QRectF rect = resultPixmap.rect(); - int x = int((rect.width() * 0.5) - (this->ThumbnailImage.rect().width() * 0.5)); - int y = int((rect.height() * 0.5) - (this->ThumbnailImage.rect().height() * 0.5)); - - QPixmap thumbnailPixmap = QPixmap::fromImage(this->ThumbnailImage); - painter.drawPixmap(x, y, thumbnailPixmap); - - QString topLeftString = ctkDICOMSeriesItemWidget::tr("Series: %1\n%2").arg(this->SeriesNumber).arg(this->Modality); - this->drawTextWithShadow(&painter, font, margin, margin, Qt::AlignTop | Qt::AlignLeft, topLeftString); - QString rows = this->DicomDatabase->instanceValue(this->CentralFrameSOPInstanceUID, "0028,0010"); - QString columns = this->DicomDatabase->instanceValue(this->CentralFrameSOPInstanceUID, "0028,0011"); - QString bottomLeftString = rows + "x" + columns + "x" + QString::number(numberOfFrames); - this->drawTextWithShadow(&painter, font, margin, rect.height() - margin * 2, - Qt::AlignBottom | Qt::AlignLeft, bottomLeftString); - QSvgRenderer renderer; - - if (this->RetrieveFailed) - { - renderer.load(QString(":Icons/error_red.svg")); - } - else if (this->IsCloud) - { - if (this->NumberOfDownloads > 0) - { - renderer.load(QString(":Icons/downloading.svg")); - } - else - { - renderer.load(QString(":Icons/cloud.svg")); - } - } - else if (this->IsVisible) + topLeftString = ctkDICOMSeriesItemWidget::tr("Series: %1").arg(this->SeriesNumber); + } + this->drawTextWithShadow(&painter, font, margin, margin, Qt::AlignTop | Qt::AlignLeft, topLeftString); + QString rows = this->DicomDatabase->instanceValue(this->CentralFrameSOPInstanceUID, "0028,0010"); + QString columns = this->DicomDatabase->instanceValue(this->CentralFrameSOPInstanceUID, "0028,0011"); + QString bottomLeftString = rows + "x" + columns + "x" + QString::number(numberOfFrames); + this->drawTextWithShadow(&painter, font, margin, rect.height() - margin * 2, + Qt::AlignBottom | Qt::AlignLeft, bottomLeftString); + QSvgRenderer renderer; + + if (this->RetrieveFailed) + { + renderer.load(QString(":Icons/error_red.svg")); + } + else if (this->IsCloud) + { + if (this->NumberOfDownloads > 0) { - renderer.load(QString(":Icons/visible.svg")); + renderer.load(QString(":Icons/downloading.svg")); } - else if (this->IsLoaded) + else { - renderer.load(QString(":Icons/loaded.svg")); + renderer.load(QString(":Icons/cloud.svg")); } - - QPointF topRight = rect.topRight(); - QRectF bounds(topRight.x() - iconSize - margin, topRight.y() + margin, iconSize, iconSize); - renderer.render(&painter, bounds); - painter.end(); } - } + else if (this->IsVisible) + { + renderer.load(QString(":Icons/visible.svg")); + } + else if (this->IsLoaded) + { + renderer.load(QString(":Icons/loaded.svg")); + } - if ((thumbnailGenerated && !this->isThumbnailDocument) || emptyThumbnailGenerated) - { - resultPixmap.setDevicePixelRatio(scalingFactor); - this->SeriesThumbnail->setPixmap(resultPixmap); + QPointF topRight = rect.topRight(); + QRectF bounds(topRight.x() - iconSize - margin, topRight.y() + margin, iconSize, iconSize); + renderer.render(&painter, bounds); + painter.end(); } + + resultPixmap.setDevicePixelRatio(scalingFactor); + this->SeriesThumbnail->setPixmap(resultPixmap); } //---------------------------------------------------------------------------- @@ -666,10 +695,12 @@ void ctkDICOMSeriesItemWidgetPrivate::updateThumbnailProgressBar() this->SeriesThumbnail->operationProgressBar()->show(); // change icons QString file = this->DicomDatabase->fileForInstance(this->CentralFrameSOPInstanceUID); - if (!file.isEmpty()) - { - this->drawThumbnail(file, numberOfFrames); - } + this->drawThumbnail(file, + this->StudyInstanceUID, + this->SeriesInstanceUID, + this->CentralFrameSOPInstanceUID, + this->Modality, + numberOfFrames); } } @@ -703,7 +734,12 @@ void ctkDICOMSeriesItemWidgetPrivate::updateRetrieveUIOnFailed() QStringList instancesList = this->DicomDatabase->instancesForSeries(this->SeriesInstanceUID); int numberOfFrames = instancesList.count(); - this->drawThumbnail(file, numberOfFrames); + this->drawThumbnail(file, + this->StudyInstanceUID, + this->SeriesInstanceUID, + this->CentralFrameSOPInstanceUID, + this->Modality, + numberOfFrames); } //---------------------------------------------------------------------------- @@ -728,13 +764,12 @@ void ctkDICOMSeriesItemWidgetPrivate::updateRetrieveUIOnFinished() this->SeriesThumbnail->operationProgressBar()->hide(); } - QString file = this->DicomDatabase->fileForInstance(this->CentralFrameSOPInstanceUID); - if (file.isEmpty()) - { - return; - } - - this->drawThumbnail(file, numberOfFrames); + this->drawThumbnail(this->DicomDatabase->fileForInstance(this->CentralFrameSOPInstanceUID), + this->StudyInstanceUID, + this->SeriesInstanceUID, + this->CentralFrameSOPInstanceUID, + this->Modality, + numberOfFrames); this->SeriesThumbnail->setOperationStatus(ctkThumbnailLabel::Completed); this->SeriesThumbnail->setStatusIcon(QIcon(":/Icons/accept.svg")); } @@ -813,6 +848,7 @@ void ctkDICOMSeriesItemWidget::forceRetrieve() d->RetrieveFailed = false; d->StopJobs = false; d->DicomDatabase->removeSeries(d->SeriesInstanceUID, false, false); + d->ThumbnailImage = QImage(); this->generateInstances(true); } @@ -897,7 +933,7 @@ void ctkDICOMSeriesItemWidget::generateInstances(bool query, bool retrieve) d->Scheduler->queryInstances(d->PatientID, d->StudyInstanceUID, d->SeriesInstanceUID, - d->RaiseJobsPriority ? QThread::HighestPriority : QThread::NormalPriority, + d->RaiseJobsPriority ? QThread::HighPriority : QThread::NormalPriority, d->AllowedServers); } } @@ -1101,8 +1137,7 @@ void ctkDICOMSeriesItemWidget::onOperationStatusButtonClicked(bool) QStringList(), QStringList(d->SeriesInstanceUID)); } - else if (status == ctkThumbnailLabel::Failed || - status == ctkThumbnailLabel::Completed) + else if (status > ctkThumbnailLabel::InProgress) { ctkDICOMJobDetail queryJobDetail; queryJobDetail.JobClass = "ctkDICOMQueryJob"; @@ -1111,25 +1146,5 @@ void ctkDICOMSeriesItemWidget::onOperationStatusButtonClicked(bool) queryJobDetail.StudyInstanceUID = d->StudyInstanceUID; queryJobDetail.SeriesInstanceUID = d->SeriesInstanceUID; d->Scheduler->runJob(queryJobDetail, d->AllowedServers); - - if (!d->CentralFrameSOPInstanceUID.isEmpty()) - { - ctkDICOMJobDetail retrieveInstanceJobDetail; - retrieveInstanceJobDetail.JobClass = "ctkDICOMRetrieveJob"; - retrieveInstanceJobDetail.DICOMLevel = ctkDICOMJob::DICOMLevels::Instances; - retrieveInstanceJobDetail.PatientID = d->PatientID; - retrieveInstanceJobDetail.StudyInstanceUID = d->StudyInstanceUID; - retrieveInstanceJobDetail.SeriesInstanceUID = d->SeriesInstanceUID; - retrieveInstanceJobDetail.SOPInstanceUID = d->CentralFrameSOPInstanceUID; - d->Scheduler->runJob(retrieveInstanceJobDetail, d->AllowedServers); - } - - ctkDICOMJobDetail retrieveSeriesJobDetail; - retrieveSeriesJobDetail.JobClass = "ctkDICOMRetrieveJob"; - retrieveSeriesJobDetail.DICOMLevel = ctkDICOMJob::DICOMLevels::Series; - retrieveSeriesJobDetail.PatientID = d->PatientID; - retrieveSeriesJobDetail.StudyInstanceUID = d->StudyInstanceUID; - retrieveSeriesJobDetail.SeriesInstanceUID = d->SeriesInstanceUID; - d->Scheduler->runJob(retrieveSeriesJobDetail, d->AllowedServers); } } diff --git a/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.cpp b/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.cpp index 4237b9ae18..a9f8992e83 100644 --- a/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.cpp @@ -375,7 +375,7 @@ void ctkDICOMServerNodeWidget2Private::init() QObject::connect(this->RestoreDefaultPushButton, SIGNAL(clicked()), q, SLOT(onRestoreDefaultServers())); this->SaveButton = this->ActionsButtonBox->button(QDialogButtonBox::StandardButton::Save); - this->SaveButton->setText(ctkDICOMServerNodeWidget2::tr("Apply changes")); + this->SaveButton->setText(ctkDICOMServerNodeWidget2::tr("Apply changes ")); this->SaveButton->setIcon(QIcon(":/Icons/save.svg")); this->CancelButton = this->ActionsButtonBox->button(QDialogButtonBox::StandardButton::Discard); this->CancelButton->setText(ctkDICOMServerNodeWidget2::tr("Discard changes")); diff --git a/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp index a15f42d22c..f81161e396 100644 --- a/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp @@ -776,12 +776,13 @@ void ctkDICOMStudyItemWidget::generateSeries(bool query, bool retrieve) d->QueryOn = query; d->RetrieveOn = retrieve; d->createSeries(); - if (query && d->Scheduler && d->Scheduler->queryRetrieveServersCount() > 0) + if (query && d->Scheduler && + d->Scheduler->queryRetrieveServersCount() > 0) { d->Scheduler->querySeries(d->PatientID, - d->StudyInstanceUID, - QThread::NormalPriority, - d->AllowedServers); + d->StudyInstanceUID, + QThread::NormalPriority, + d->AllowedServers); } } @@ -922,8 +923,7 @@ void ctkDICOMStudyItemWidget::onOperationStatusButtonClicked(bool) d->Scheduler->stopJobsByDICOMUIDs(QStringList(), QStringList(d->StudyInstanceUID)); } - else if (status == ctkDICOMStudyItemWidget::Failed || - status == ctkDICOMStudyItemWidget::Completed) + else if (status > ctkDICOMStudyItemWidget::InProgress) { ctkDICOMJobDetail queryJobDetail; queryJobDetail.JobClass = "ctkDICOMQueryJob"; diff --git a/Libs/DICOM/Widgets/ctkDICOMThumbnailGenerator.cpp b/Libs/DICOM/Widgets/ctkDICOMThumbnailGenerator.cpp index aa084c4cbc..c57819d6c7 100644 --- a/Libs/DICOM/Widgets/ctkDICOMThumbnailGenerator.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMThumbnailGenerator.cpp @@ -27,6 +27,8 @@ #include #include #include +#include +#include // DCMTK includes #include "dcmtk/dcmimgle/dcmimage.h" @@ -189,25 +191,27 @@ bool ctkDICOMThumbnailGenerator::generateThumbnail(DicomImage *dcmImage, QImage& } //------------------------------------------------------------------------------ -bool ctkDICOMThumbnailGenerator::generateThumbnail(DicomImage *dcmImage, const QString &path) +bool ctkDICOMThumbnailGenerator::generateThumbnail(DicomImage *dcmImage, const QString &thumbnailPath, QVector color) { QImage image; if (this->generateThumbnail(dcmImage, image)) { - return image.save(path,"PNG"); + return image.save(thumbnailPath, "PNG"); } + + this->generateDocumentThumbnail(thumbnailPath, color); return false; } //------------------------------------------------------------------------------ -bool ctkDICOMThumbnailGenerator::generateThumbnail(const QString dcmImagePath, QImage& image) +bool ctkDICOMThumbnailGenerator::generateThumbnail(const QString& dcmImagePath, QImage& image) { DicomImage dcmImage(QDir::toNativeSeparators(dcmImagePath).toUtf8()); return this->generateThumbnail(&dcmImage, image); } //------------------------------------------------------------------------------ -bool ctkDICOMThumbnailGenerator::generateThumbnail(const QString dcmImagePath, const QString& thumbnailPath) +bool ctkDICOMThumbnailGenerator::generateThumbnail(const QString& dcmImagePath, const QString& thumbnailPath) { DicomImage dcmImage(QDir::toNativeSeparators(dcmImagePath).toUtf8()); return this->generateThumbnail(&dcmImage, thumbnailPath); @@ -223,3 +227,21 @@ void ctkDICOMThumbnailGenerator::generateBlankThumbnail(QImage& image, QColor co } image.fill(color); } + +//------------------------------------------------------------------------------ +void ctkDICOMThumbnailGenerator::generateDocumentThumbnail(const QString &thumbnailPath, QVector color) +{ + QImage image; + this->generateBlankThumbnail(image, QColor(color[0], color[1], color[2])); + QPixmap pixmap = QPixmap::fromImage(image); + QPainter painter; + if (painter.begin(&pixmap)) + { + painter.setRenderHint(QPainter::Antialiasing); + QSvgRenderer renderer(QString(":Icons/text_document.svg")); + renderer.render(&painter); + painter.end(); + } + image = pixmap.toImage(); + image.save(thumbnailPath, "PNG"); +} diff --git a/Libs/DICOM/Widgets/ctkDICOMThumbnailGenerator.h b/Libs/DICOM/Widgets/ctkDICOMThumbnailGenerator.h index eb8db9a9e8..3b22f9703f 100644 --- a/Libs/DICOM/Widgets/ctkDICOMThumbnailGenerator.h +++ b/Libs/DICOM/Widgets/ctkDICOMThumbnailGenerator.h @@ -49,15 +49,17 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMThumbnailGenerator : public ctkDICOMAbstr explicit ctkDICOMThumbnailGenerator(QObject* parent = 0); virtual ~ctkDICOMThumbnailGenerator(); - virtual bool generateThumbnail(DicomImage* dcmImage, const QString& path); + virtual bool generateThumbnail(DicomImage* dcmImage, const QString& thumbnailPath, + QVector color = QVector{169, 169, 169}); Q_INVOKABLE bool generateThumbnail(DicomImage *dcmImage, QImage& image); - Q_INVOKABLE bool generateThumbnail(const QString dcmImagePath, QImage& image); - Q_INVOKABLE bool generateThumbnail(const QString dcmImagePath, const QString& thumbnailPath); + Q_INVOKABLE bool generateThumbnail(const QString& dcmImagePath, QImage& image); + Q_INVOKABLE bool generateThumbnail(const QString& dcmImagePath, const QString& thumbnailPath); /// 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 virtual void generateDocumentThumbnail(const QString &thumbnailPath, QVector color = QVector{169, 169, 169}); /// Set thumbnail width void setWidth(int width); diff --git a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp index adf8c4c763..c720752def 100644 --- a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp @@ -50,6 +50,7 @@ #include "ctkDICOMScheduler.h" #include "ctkDICOMScheduler.h" #include "ctkDICOMServer.h" +#include "ctkDICOMThumbnailGenerator.h" #include "ctkUtils.h" // ctkDICOMWidgets includes @@ -227,6 +228,7 @@ class ctkDICOMVisualBrowserWidgetPrivate : public Ui_ctkDICOMVisualBrowserWidget QString DatabaseDirectory; QSharedPointer DicomDatabase; + QSharedPointer ThumbnailGenerator; QSharedPointer Scheduler; QSharedPointer Indexer; @@ -262,7 +264,10 @@ CTK_SET_CPP(ctkDICOMVisualBrowserWidget, const QString&, setDatabaseDirectoryBas ctkDICOMVisualBrowserWidgetPrivate::ctkDICOMVisualBrowserWidgetPrivate(ctkDICOMVisualBrowserWidget& obj) : q_ptr(&obj) { + this->ThumbnailGenerator = QSharedPointer(new ctkDICOMThumbnailGenerator); + this->DicomDatabase = QSharedPointer(new ctkDICOMDatabase); + this->DicomDatabase->setThumbnailGenerator(ThumbnailGenerator.data()); this->Scheduler = QSharedPointer(new ctkDICOMScheduler); this->Scheduler->setDicomDatabase(this->DicomDatabase); @@ -3215,8 +3220,7 @@ void ctkDICOMVisualBrowserWidget::onOperationStatusTabBarItemClicked(int index) { d->Scheduler->stopJobsByDICOMUIDs(QStringList(patientItemWidget->patientID())); } - else if (status == ctkDICOMPatientItemWidget::Failed || - status == ctkDICOMPatientItemWidget::Completed) + else if (status > ctkDICOMPatientItemWidget::InProgress) { ctkDICOMJobDetail queryJobDetail; queryJobDetail.JobClass = "ctkDICOMQueryJob";