Skip to content

Commit

Permalink
ENH: Add DICOM displayed field generator rule factory
Browse files Browse the repository at this point in the history
- DICOM displayed rules are created for every new database using a factory class
  - Allows the indexer to use an up-to-date list of generator rules when updating the displayed fields after importing new data
- The displayed field generator sets up the displayed field rules when setting the database, and also on each startUpdate so that it always has an up-to-date rule list
- The factory uses a new DisplayedFieldGeneratorRules table in the database for providing the proper rule set
  - The table defines for each rule (by name) whether they are enabled and may provide an options string (for later use)
  - Database schema version has been increased to 0.7.0
  - If the table does not exist (e.g. using another or a custom schema) or if there is no entry for a rule (omitted from the list for any reason), then the rule is treated as enabled by default. So basically it is enough to specify the disabled rules in this table
  • Loading branch information
cpinter authored and lassoan committed May 28, 2021
1 parent dc2e128 commit 01ab373
Show file tree
Hide file tree
Showing 22 changed files with 534 additions and 88 deletions.
15 changes: 9 additions & 6 deletions Libs/DICOM/Core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,21 @@ set(KIT_SRCS
ctkDICOMTester.h
ctkDICOMUtil.cpp
ctkDICOMUtil.h
ctkDICOMDisplayedFieldGeneratorRuleFactory.h
ctkDICOMDisplayedFieldGeneratorRuleFactory.cpp
ctkDICOMDisplayedFieldGeneratorAbstractRule.h
ctkDICOMDisplayedFieldGeneratorDefaultRule.h
ctkDICOMDisplayedFieldGeneratorDefaultRule.cpp
ctkDICOMDisplayedFieldGeneratorRadiotherapySeriesDescriptionRule.h
ctkDICOMDisplayedFieldGeneratorRadiotherapySeriesDescriptionRule.cpp
ctkDICOMDisplayedFieldGeneratorLastStudyDateRule.h
ctkDICOMDisplayedFieldGeneratorLastStudyDateRule.cpp
ctkDICOMDisplayedFieldGeneratorSeriesImageCount.h
ctkDICOMDisplayedFieldGeneratorSeriesImageCount.cpp
ctkDICOMDisplayedFieldGeneratorStudyNumberOfSeries.h
ctkDICOMDisplayedFieldGeneratorStudyNumberOfSeries.cpp
ctkDICOMDisplayedFieldGeneratorPatientNumberOfStudies.h
ctkDICOMDisplayedFieldGeneratorPatientNumberOfStudies.cpp
ctkDICOMDisplayedFieldGeneratorSeriesImageCountRule.h
ctkDICOMDisplayedFieldGeneratorSeriesImageCountRule.cpp
ctkDICOMDisplayedFieldGeneratorStudyNumberOfSeriesRule.h
ctkDICOMDisplayedFieldGeneratorStudyNumberOfSeriesRule.cpp
ctkDICOMDisplayedFieldGeneratorPatientNumberOfStudiesRule.h
ctkDICOMDisplayedFieldGeneratorPatientNumberOfStudiesRule.cpp
)

# Abstract class should not be wrapped !
Expand All @@ -63,6 +65,7 @@ set(KIT_MOC_SRCS
ctkDICOMDatabase.h
ctkDICOMDisplayedFieldGenerator.h
ctkDICOMDisplayedFieldGenerator_p.h
ctkDICOMDisplayedFieldGeneratorRuleFactory.h
ctkDICOMIndexer.h
ctkDICOMIndexer_p.h
ctkDICOMFilterProxyModel.h
Expand Down
9 changes: 8 additions & 1 deletion Libs/DICOM/Core/Resources/dicom-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ DROP TABLE IF EXISTS 'Patients' ;
DROP TABLE IF EXISTS 'Series' ;
DROP TABLE IF EXISTS 'Studies' ;
DROP TABLE IF EXISTS 'ColumnDisplayProperties' ;
DROP TABLE IF EXISTS 'DisplayedFieldGeneratorRules' ;
DROP TABLE IF EXISTS 'Directories' ;

DROP INDEX IF EXISTS 'ImagesFilenameIndex' ;
Expand All @@ -22,7 +23,7 @@ DROP INDEX IF EXISTS 'SeriesStudyIndex' ;
DROP INDEX IF EXISTS 'StudiesPatientIndex' ;

CREATE TABLE 'SchemaInfo' ( 'Version' VARCHAR(1024) NOT NULL );
INSERT INTO 'SchemaInfo' VALUES('0.6.3');
INSERT INTO 'SchemaInfo' VALUES('0.7.0');

CREATE TABLE 'Images' (
'SOPInstanceUID' VARCHAR(64) NOT NULL,
Expand Down Expand Up @@ -149,3 +150,9 @@ INSERT INTO 'ColumnDisplayProperties' VALUES('Series', 'DisplayedSize',
INSERT INTO 'ColumnDisplayProperties' VALUES('Series', 'DisplayedCount', 'Count', 1, 5, '{"resizeMode":"resizeToContents"}');
INSERT INTO 'ColumnDisplayProperties' VALUES('Series', 'DisplayedNumberOfFrames', 'Number of frames', 0, 0, '');
INSERT INTO 'ColumnDisplayProperties' VALUES('Series', 'DisplayedFieldsUpdatedTimestamp', '', 0, 0, '');

CREATE TABLE 'DisplayedFieldGeneratorRules' (
'Name' VARCHAR(64) NOT NULL,
'Enabled' INT NULL DEFAULT 1 ,
'Options' VARCHAR(255) NULL ,
PRIMARY KEY ('Name') );
28 changes: 23 additions & 5 deletions Libs/DICOM/Core/ctkDICOMCorePythonQtDecorators.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <PythonQt.h>

// CTK includes
#include <ctkDICOMDisplayedFieldGeneratorRuleFactory.h>
#include <ctkDICOMUtil.h>

// NOTE:
Expand All @@ -44,20 +45,37 @@ class ctkDICOMCorePythonQtDecorators : public QObject
{
}

public Q_SLOTS:
public slots:

//
// None yet - refer to other libs for examples
//
//----------------------------------------------------------------------------
// ctkDICOMDisplayedFieldGeneratorRuleFactory

//----------------------------------------------------------------------------
// static methods

//----------------------------------------------------------------------------
ctkDICOMDisplayedFieldGeneratorRuleFactory* static_ctkDICOMDisplayedFieldGeneratorRuleFactory_instance()
{
return ctkDICOMDisplayedFieldGeneratorRuleFactory::instance();
}

//----------------------------------------------------------------------------
// instance methods

//----------------------------------------------------------------------------
bool registerDisplayedFieldGeneratorRule(ctkDICOMDisplayedFieldGeneratorRuleFactory* factory,
PythonQtPassOwnershipToCPP<ctkDICOMDisplayedFieldGeneratorAbstractRule*> plugin)
{
return factory->registerDisplayedFieldGeneratorRule(plugin);
}
};

//-----------------------------------------------------------------------------
class PythonQtWrapper_CTKDICOMCore : public QObject
{
Q_OBJECT

public Q_SLOTS:
public slots:
ctkErrorLogLevel::LogLevel static_ctk_dicomLogLevel()
{
return ctk::dicomLogLevel();
Expand Down
8 changes: 4 additions & 4 deletions Libs/DICOM/Core/ctkDICOMDatabase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ ctkDICOMDatabasePrivate::ctkDICOMDatabasePrivate(ctkDICOMDatabase& o)
, TagCacheVerified(false)
, DisplayedFieldsTableAvailable(false)
, UseShortStoragePath(true)
, SchemaVersion("0.6.3")
, SchemaVersion("0.7.0")
{
this->resetLastInsertedValues();
this->DisplayedFieldGenerator = new ctkDICOMDisplayedFieldGenerator(q_ptr);
Expand All @@ -103,7 +103,8 @@ void ctkDICOMDatabasePrivate::init(QString databaseFilename)
}

//------------------------------------------------------------------------------
void ctkDICOMDatabasePrivate::registerCompressionLibraries(){
void ctkDICOMDatabasePrivate::registerCompressionLibraries()
{
// Register the JPEG libraries in case we need them
// (registration only happens once, so it's okay to call repeatedly)
// register global JPEG decompression codecs
Expand Down Expand Up @@ -1555,6 +1556,7 @@ void ctkDICOMDatabase::openDatabase(const QString databaseFile, const QString& c
// Add displayed field generator's required tags to the pre-cached list to make
// displayed field updates fast.
QStringList tags = this->tagsToPrecache();
d->DisplayedFieldGenerator->setDatabase(this);
tags << d->DisplayedFieldGenerator->getRequiredTags();
tags.removeDuplicates();
this->setTagsToPrecache(tags);
Expand Down Expand Up @@ -2980,8 +2982,6 @@ void ctkDICOMDatabase::updateDisplayedFields()
QMap<QString /*StudyInstanceUID*/, QMap<QString /*DisplayField*/, QString /*Value*/> > displayedFieldsMapStudy;
QMap<QString /*CompositePatientID*/, QMap<QString /*DisplayField*/, QString /*Value*/> > displayedFieldsMapPatient;

d->DisplayedFieldGenerator->setDatabase(this);

int progressValue = 0;
emit displayedFieldsUpdateProgress(++progressValue);

Expand Down
96 changes: 59 additions & 37 deletions Libs/DICOM/Core/ctkDICOMDisplayedFieldGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,11 @@

// ctkDICOM includes
#include "ctkLogger.h"
#include "ctkDICOMDatabase.h"
#include "ctkDICOMDisplayedFieldGenerator.h"
#include "ctkDICOMDisplayedFieldGenerator_p.h"

#include "ctkDICOMDatabase.h"
#include "ctkDICOMDisplayedFieldGeneratorDefaultRule.h"
#include "ctkDICOMDisplayedFieldGeneratorRadiotherapySeriesDescriptionRule.h"
#include "ctkDICOMDisplayedFieldGeneratorLastStudyDateRule.h"
#include "ctkDICOMDisplayedFieldGeneratorSeriesImageCount.h"
#include "ctkDICOMDisplayedFieldGeneratorStudyNumberOfSeries.h"
#include "ctkDICOMDisplayedFieldGeneratorPatientNumberOfStudies.h"
#include "ctkDICOMDisplayedFieldGeneratorAbstractRule.h"
#include "ctkDICOMDisplayedFieldGeneratorRuleFactory.h"

//------------------------------------------------------------------------------
static ctkLogger logger("org.commontk.dicom.DICOMDisplayedFieldGenerator" );
Expand All @@ -47,45 +42,77 @@ ctkDICOMDisplayedFieldGeneratorPrivate::ctkDICOMDisplayedFieldGeneratorPrivate(c
: q_ptr(&o)
, Database(nullptr)
{
// register commonly used rules
this->AllRules.append(new ctkDICOMDisplayedFieldGeneratorDefaultRule);
this->AllRules.append(new ctkDICOMDisplayedFieldGeneratorRadiotherapySeriesDescriptionRule);
this->AllRules.append(new ctkDICOMDisplayedFieldGeneratorLastStudyDateRule);
this->AllRules.append(new ctkDICOMDisplayedFieldGeneratorSeriesImageCount);
this->AllRules.append(new ctkDICOMDisplayedFieldGeneratorStudyNumberOfSeries);
this->AllRules.append(new ctkDICOMDisplayedFieldGeneratorPatientNumberOfStudies);

foreach(ctkDICOMDisplayedFieldGeneratorAbstractRule* rule, this->AllRules)
}

//------------------------------------------------------------------------------
ctkDICOMDisplayedFieldGeneratorPrivate::~ctkDICOMDisplayedFieldGeneratorPrivate() = default;

//------------------------------------------------------------------------------
void ctkDICOMDisplayedFieldGeneratorPrivate::setupEnabledDisplayedFieldGeneratorRules()
{
if (!this->Database)
{
qCritical() << Q_FUNC_INFO << " failed: DICOM database needs to be set";
return;
}
if (!this->GeneratorRules.isEmpty())
{
qWarning() << Q_FUNC_INFO << " : Generator rules have not been cleared before new update session";

this->clearDisplayedFieldGeneratorRules();
}

// Instantiate enabled rules registered in factory
this->GeneratorRules =
ctkDICOMDisplayedFieldGeneratorRuleFactory::instance()->copyEnabledDisplayedFieldGeneratorRules(this->Database);

// Setup generator rules used in this update session
foreach(ctkDICOMDisplayedFieldGeneratorAbstractRule* rule, this->GeneratorRules)
{
// Set database
rule->setDatabase(this->Database);
// Register empty field names
rule->registerEmptyFieldNames(
this->EmptyFieldNamesSeries, this->EmptyFieldNamesStudies, this->EmptyFieldNamesPatients );
}

//TODO: Process Options field in DisplayedFieldGeneratorRules table when has a concrete use.
}

//------------------------------------------------------------------------------
ctkDICOMDisplayedFieldGeneratorPrivate::~ctkDICOMDisplayedFieldGeneratorPrivate()
void ctkDICOMDisplayedFieldGeneratorPrivate::clearDisplayedFieldGeneratorRules()
{
foreach(ctkDICOMDisplayedFieldGeneratorAbstractRule* rule, this->AllRules)
// Delete generator rules and clear container
foreach(ctkDICOMDisplayedFieldGeneratorAbstractRule* rule, this->GeneratorRules)
{
delete rule;
}
this->AllRules.clear();
this->GeneratorRules.clear();

// Clear empty field names registered previously by the rules
this->EmptyFieldNamesSeries.clear();
this->EmptyFieldNamesStudies.clear();
this->EmptyFieldNamesPatients.clear();
}


//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
// ctkDICOMDisplayedFieldGenerator methods

//------------------------------------------------------------------------------
ctkDICOMDisplayedFieldGenerator::ctkDICOMDisplayedFieldGenerator(QObject *parent):d_ptr(new ctkDICOMDisplayedFieldGeneratorPrivate(*this))
ctkDICOMDisplayedFieldGenerator::ctkDICOMDisplayedFieldGenerator(QObject *parent)
: d_ptr(new ctkDICOMDisplayedFieldGeneratorPrivate(*this))
{
Q_UNUSED(parent);
}

//------------------------------------------------------------------------------
ctkDICOMDisplayedFieldGenerator::~ctkDICOMDisplayedFieldGenerator()
{
Q_D(ctkDICOMDisplayedFieldGenerator);
d->clearDisplayedFieldGeneratorRules();
}

//------------------------------------------------------------------------------
Expand All @@ -94,13 +121,12 @@ QStringList ctkDICOMDisplayedFieldGenerator::getRequiredTags()
Q_D(ctkDICOMDisplayedFieldGenerator);

QStringList requiredTags;
foreach(ctkDICOMDisplayedFieldGeneratorAbstractRule* rule, d->AllRules)
foreach(ctkDICOMDisplayedFieldGeneratorAbstractRule* rule, d->GeneratorRules)
{
requiredTags << rule->getRequiredDICOMTags();
}
requiredTags.removeDuplicates();

// TODO: remove duplicates from requiredTags (maybe also sort)
return requiredTags;
}

Expand All @@ -117,7 +143,7 @@ void ctkDICOMDisplayedFieldGenerator::updateDisplayedFieldsForInstance(
QMap<QString, QString> newFieldsSeries;
QMap<QString, QString> newFieldsStudy;
QMap<QString, QString> newFieldsPatient;
foreach(ctkDICOMDisplayedFieldGeneratorAbstractRule* rule, d->AllRules)
foreach(ctkDICOMDisplayedFieldGeneratorAbstractRule* rule, d->GeneratorRules)
{
QMap<QString, QString> initialFieldsSeries = displayedFieldsForCurrentSeries;
QMap<QString, QString> initialFieldsStudy = displayedFieldsForCurrentStudy;
Expand All @@ -138,7 +164,12 @@ void ctkDICOMDisplayedFieldGenerator::updateDisplayedFieldsForInstance(
void ctkDICOMDisplayedFieldGenerator::startUpdate()
{
Q_D(ctkDICOMDisplayedFieldGenerator);
foreach(ctkDICOMDisplayedFieldGeneratorAbstractRule* rule, d->AllRules)

// Re-initialize generator rules in case new ones have been registered or the rule options changed in the database
d->clearDisplayedFieldGeneratorRules();
d->setupEnabledDisplayedFieldGeneratorRules();

foreach(ctkDICOMDisplayedFieldGeneratorAbstractRule* rule, d->GeneratorRules)
{
rule->startUpdate();
}
Expand All @@ -150,7 +181,8 @@ void ctkDICOMDisplayedFieldGenerator::endUpdate(QMap<QString, QMap<QString, QStr
QMap<QString, QMap<QString, QString> > &displayedFieldsMapPatient)
{
Q_D(ctkDICOMDisplayedFieldGenerator);
foreach(ctkDICOMDisplayedFieldGeneratorAbstractRule* rule, d->AllRules)

foreach(ctkDICOMDisplayedFieldGeneratorAbstractRule* rule, d->GeneratorRules)
{
rule->endUpdate(displayedFieldsMapSeries, displayedFieldsMapStudy, displayedFieldsMapPatient);
}
Expand All @@ -168,15 +200,5 @@ void ctkDICOMDisplayedFieldGenerator::setDatabase(ctkDICOMDatabase* database)

d->Database = database;

foreach(ctkDICOMDisplayedFieldGeneratorAbstractRule* rule, d->AllRules)
{
rule->setDatabase(database);
}
}

//------------------------------------------------------------------------------
void ctkDICOMDisplayedFieldGenerator::registerDisplayedFieldGeneratorRule(ctkDICOMDisplayedFieldGeneratorAbstractRule* rule)
{
Q_D(ctkDICOMDisplayedFieldGenerator);
d->AllRules.append(rule);
d->setupEnabledDisplayedFieldGeneratorRules();
}
5 changes: 1 addition & 4 deletions Libs/DICOM/Core/ctkDICOMDisplayedFieldGenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMDisplayedFieldGenerator : public QObject
{
Q_OBJECT
public:
explicit ctkDICOMDisplayedFieldGenerator(QObject *parent = 0);
explicit ctkDICOMDisplayedFieldGenerator(QObject* parent);
virtual ~ctkDICOMDisplayedFieldGenerator();

/// Set DICOM database
Expand Down Expand Up @@ -79,9 +79,6 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMDisplayedFieldGenerator : public QObject
QMap<QString, QMap<QString, QString> > &displayedFieldsMapStudy,
QMap<QString, QMap<QString, QString> > &displayedFieldsMapPatient);

/// Register new displayed field generator rule
void registerDisplayedFieldGeneratorRule(ctkDICOMDisplayedFieldGeneratorAbstractRule* rule);

protected:
QScopedPointer<ctkDICOMDisplayedFieldGeneratorPrivate> d_ptr;

Expand Down
6 changes: 6 additions & 0 deletions Libs/DICOM/Core/ctkDICOMDisplayedFieldGeneratorAbstractRule.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMDisplayedFieldGeneratorAbstractRule
public:
virtual ~ctkDICOMDisplayedFieldGeneratorAbstractRule(){}

/// Get name of rule
virtual QString name()const = 0;

/// Clone displayed field generator rule. Override to return a new instance of the rule sub-class
virtual ctkDICOMDisplayedFieldGeneratorAbstractRule* clone() = 0;

/// Generate displayed fields for a certain instance based on its cached tags
/// Each rule plugin has the chance to fill any field in the series, study, and patient fields.
/// The way these generated fields will be used is defined by \sa mergeDisplayedFieldsForInstance
Expand Down
12 changes: 12 additions & 0 deletions Libs/DICOM/Core/ctkDICOMDisplayedFieldGeneratorDefaultRule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@
// dcmtk includes
#include "dcmtk/dcmdata/dcvrpn.h"

//------------------------------------------------------------------------------
QString ctkDICOMDisplayedFieldGeneratorDefaultRule::name()const
{
return "Default";
}

//------------------------------------------------------------------------------
ctkDICOMDisplayedFieldGeneratorAbstractRule* ctkDICOMDisplayedFieldGeneratorDefaultRule::clone()
{
return new ctkDICOMDisplayedFieldGeneratorDefaultRule();
}

//------------------------------------------------------------------------------
QStringList ctkDICOMDisplayedFieldGeneratorDefaultRule::getRequiredDICOMTags()
{
Expand Down
6 changes: 6 additions & 0 deletions Libs/DICOM/Core/ctkDICOMDisplayedFieldGeneratorDefaultRule.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@
class CTK_DICOM_CORE_EXPORT ctkDICOMDisplayedFieldGeneratorDefaultRule : public ctkDICOMDisplayedFieldGeneratorAbstractRule
{
public:
/// Get name of rule
QString name()const override;

/// Clone displayed field generator rule. Override to return a new instance of the rule sub-class
ctkDICOMDisplayedFieldGeneratorAbstractRule* clone() override;

/// Specify list of DICOM tags required by the rule. These tags will be included in the tag cache
QStringList getRequiredDICOMTags() override;

Expand Down
Loading

0 comments on commit 01ab373

Please sign in to comment.