Skip to content

Commit

Permalink
ENH: Generate thumbnails in workers
Browse files Browse the repository at this point in the history
  • Loading branch information
Punzo committed Aug 30, 2024
1 parent d32ad6b commit b9bc53d
Show file tree
Hide file tree
Showing 22 changed files with 723 additions and 43 deletions.
15 changes: 14 additions & 1 deletion Libs/DICOM/Core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions Libs/DICOM/Core/ctkDICOMAbstractThumbnailGenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

// Qt includes
#include <QObject>
#include <QVector>
#include <QColor>

#include "ctkDICOMCoreExport.h"

Expand All @@ -45,9 +45,9 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMAbstractThumbnailGenerator : public QObject
virtual ~ctkDICOMAbstractThumbnailGenerator();

virtual bool generateThumbnail(DicomImage* dcmImage, const QString& path,
QVector<int> color = QVector<int>{169, 169, 169}) = 0;
QColor backgroundColor = Qt::darkGray) = 0;
virtual void generateDocumentThumbnail(const QString &thumbnailPath,
QVector<int> color = QVector<int>{169, 169, 169}) = 0;
QColor backgroundColor = Qt::darkGray) = 0;

protected:
QScopedPointer<ctkDICOMAbstractThumbnailGeneratorPrivate> d_ptr;
Expand Down
6 changes: 3 additions & 3 deletions Libs/DICOM/Core/ctkDICOMDatabase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2374,7 +2374,7 @@ bool ctkDICOMDatabase::storeThumbnailFile(const QString &originalFilePath,
const QString &seriesInstanceUID,
const QString &sopInstanceUID,
const QString& modality,
QVector<int> color)
QColor backgroundColor)
{
Q_D(ctkDICOMDatabase);
if (!d->ThumbnailGenerator)
Expand Down Expand Up @@ -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);
}
}

Expand Down
3 changes: 2 additions & 1 deletion Libs/DICOM/Core/ctkDICOMDatabase.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#define __ctkDICOMDatabase_h

// Qt includes
#include <QColor>
#include <QObject>
#include <QStringList>
#include <QSqlDatabase>
Expand Down Expand Up @@ -216,7 +217,7 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMDatabase : public QObject
const QString& seriesInstanceUID,
const QString& sopInstanceUID,
const QString& modality = "",
QVector<int> color = QVector<int>{169, 169, 169});
QColor backgroundColor = Qt::darkGray);

Q_INVOKABLE int patientsCount();
Q_INVOKABLE int studiesCount();
Expand Down
2 changes: 1 addition & 1 deletion Libs/DICOM/Core/ctkDICOMEchoJob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion Libs/DICOM/Core/ctkDICOMIndexer_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,6 @@ class ctkDICOMIndexerPrivate : public QObject
Q_SIGNALS:
void startWorker();


public:
DICOMIndexingQueue RequestQueue;
QThread WorkerThread;
Expand Down
1 change: 0 additions & 1 deletion Libs/DICOM/Core/ctkDICOMJob.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMJob : public ctkAbstractJob

Q_SIGNALS:
void progressJobDetail(QVariant);
void finishedJobDetail(QVariant);

protected:
QString PatientID;
Expand Down
3 changes: 2 additions & 1 deletion Libs/DICOM/Core/ctkDICOMJobResponseSet.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
79 changes: 76 additions & 3 deletions Libs/DICOM/Core/ctkDICOMScheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

// ctkDICOMCore includes
#include "ctkDICOMEchoJob.h"
#include "ctkDICOMThumbnailGeneratorJob.h"
#include "ctkDICOMInserterJob.h"
#include "ctkDICOMJobResponseSet.h"
#include "ctkDICOMQueryJob.h"
Expand Down Expand Up @@ -246,6 +247,8 @@ void ctkDICOMSchedulerPrivate::handleJobByType(const ctkDICOMJobDetail& jd,
break;
case ctkDICOMJobResponseSet::JobType::Echo:
break;
case ctkDICOMJobResponseSet::JobType::ThumbnailGenerator:
break;
case ctkDICOMJobResponseSet::JobType::QueryPatients:
jd.JobUID.isEmpty() ? q->queryPatients() : q->queryPatients(QThread::NormalPriority, jd.ConnectionName, jd.JobUID);
break;
Expand Down Expand Up @@ -276,6 +279,44 @@ void ctkDICOMSchedulerPrivate::handleJobByType(const ctkDICOMJobDetail& jd,
}
}

//------------------------------------------------------------------------------
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<ctkAbstractJob> job, this->JobsQueue)
{
if (!job)
{
continue;
}

ctkDICOMJob* dicomJob = qobject_cast<ctkDICOMJob*>(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())
{
duplicate = true;
break;
}
}
}

return duplicate;
}

//------------------------------------------------------------------------------
// ctkDICOMScheduler methods

Expand Down Expand Up @@ -510,6 +551,7 @@ void ctkDICOMScheduler::retrieveSeries(const QString& patientID,
{
continue;
}
qDebug() << allowedSeversForPatient;
if (!d->isServerAllowed(server.data(), allowedSeversForPatient))
{
continue;
Expand Down Expand Up @@ -635,6 +677,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<ctkDICOMThumbnailGeneratorJob> job =
QSharedPointer<ctkDICOMThumbnailGeneratorJob>(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<ctkDICOMJobResponseSet>& jobResponseSet,
QThread::Priority priority)
Expand Down Expand Up @@ -1096,15 +1169,15 @@ void ctkDICOMScheduler::runJob(const ctkDICOMJobDetail& jd, const QStringList& a

if (jd.JobClass == "ctkDICOMQueryJob")
{
d->handleQueryJob(jd, allowedSeversForPatient);
d->handleQueryJob(jd, allowedSevers);
}
else if (jd.JobClass == "ctkDICOMRetrieveJob")
{
d->handleRetrieveJob(jd, allowedSeversForPatient);
d->handleRetrieveJob(jd, allowedSevers);
}
else
{
d->handleJobByType(jd, allowedSeversForPatient);
d->handleJobByType(jd, allowedSevers);
}
}

Expand Down
10 changes: 10 additions & 0 deletions Libs/DICOM/Core/ctkDICOMScheduler.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,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<ctkDICOMJobResponseSet>& jobResponseSet,
Expand Down
2 changes: 2 additions & 0 deletions Libs/DICOM/Core/ctkDICOMScheduler_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ class ctkDICOMSchedulerPrivate : public ctkJobSchedulerPrivate
void handleJobByType(const ctkDICOMJobDetail& jd,
const QStringList& allowedServers = QStringList());

bool isJobDuplicate(ctkDICOMJob* job);

QSharedPointer<ctkDICOMDatabase> DicomDatabase;
QList<QSharedPointer<ctkDICOMServer>> Servers;
QMap<QString, QVariant> Filters;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,15 +191,16 @@ bool ctkDICOMThumbnailGenerator::generateThumbnail(DicomImage *dcmImage, QImage&
}

//------------------------------------------------------------------------------
bool ctkDICOMThumbnailGenerator::generateThumbnail(DicomImage *dcmImage, const QString &thumbnailPath, QVector<int> color)
bool ctkDICOMThumbnailGenerator::generateThumbnail(DicomImage *dcmImage, const QString &thumbnailPath,
QColor backgroundColor)
{
QImage image;
if (this->generateThumbnail(dcmImage, image))
{
return image.save(thumbnailPath, "PNG");
}

this->generateDocumentThumbnail(thumbnailPath, color);
this->generateDocumentThumbnail(thumbnailPath, backgroundColor);
return false;
}

Expand All @@ -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<int> 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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -50,17 +50,17 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMThumbnailGenerator : public ctkDICOMAbstr
virtual ~ctkDICOMThumbnailGenerator();

virtual bool generateThumbnail(DicomImage* dcmImage, const QString& thumbnailPath,
QVector<int> color = QVector<int>{169, 169, 169});
QColor backgroundColor = Qt::darkGray);

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);

/// 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<int> color = QVector<int>{169, 169, 169});
QColor backgroundColor = Qt::darkGray);

/// Set thumbnail width
void setWidth(int width);
Expand Down
Loading

0 comments on commit b9bc53d

Please sign in to comment.