ImFusion C++ SDK 4.4.0
Extensions

Extending the default DICOM import/export with custom functionality. More...

Collaboration diagram for Extensions:

Detailed Description

Extending the default DICOM import/export with custom functionality.

Implementing a Dicom::Extension is the best way if you need to load/store additional metadata, like a custom DataComponent or change certain parameters of the image data. See Extensions for more information. When loading, the Extensions are run sequentially after the full SharedImageSet was loaded by a Dicom::IOD. In the case of saving to DICOM, the Extensions are run sequentially after the corresponding dcmtk datasets have been created by a Dicom::IOD. Each Extension is called for each frame that the Dicom::IOD generated.

Creating a Dicom::Extension

Let's look at an example which reads the tags "XRay Tube Current" and "Convolution Kernel" and stores them in a custom DataComponent of the SharedImageSet:

#include <ImFusion/Base/DataComponentList.h>
#include <ImFusion/Base/SharedImageSet.h>
#include <ImFusion/Dicom/DicomElement.h>
#include <ImFusion/Dicom/DicomIOD.h>
#include <ImFusion/Dicom/Extension.h>
#include <dcmtk/dcmdata/dctk.h>
using namespace ImFusion;
class ExampleDataComponent : public DataComponent<ExampleDataComponent>
{
public:
ExampleDataComponent() = default;
ExampleDataComponent(const ExampleDataComponent& other) = default;
ExampleDataComponent& operator=(const ExampleDataComponent& other) = default;
bool operator==(const ExampleDataComponent& other) const
{
return other.xrayTubeCurrent == xrayTubeCurrent && other.convolutionKernel == convolutionKernel;
}
std::string id() const override { return ""; }
void configure(const Properties* p) override {};
void configuration(Properties* p) const override {};
int xrayTubeCurrent = 0;
std::string convolutionKernel = "";
};
class ExtensionExample : public Dicom::Extension
{
public:
std::unique_ptr<Extension> clone() const override { return std::make_unique<ExtensionExample>(*this); };
std::string name() const override { return "Extension Example"; }
std::vector<std::unique_ptr<Data>> load(const Dicom::FrameDescriptor& frame, SharedImageSet& sis) const override
{
// In this example we load the tags once for the complete SharedImageSet.
// Therefore, we only perform the loading on the very first frame.
// Of course you could also load different metadata for different frames.
if (frame.sharedImageIndex != 0 || frame.sliceIndex != 0)
return {};
// Instead of using Dicom::Element, you can also use the DcmItem through `frame.iod->item()` directly.
Dicom::Element<int> xrayTubeCurrent(*frame.iod, DCM_XRayTubeCurrent);
Dicom::Element<std::string> convolutionKernel(*frame.iod, DCM_ConvolutionKernel);
auto& dc = sis.components().getOrCreate<ExampleDataComponent>();
if (xrayTubeCurrent.isValid())
dc.xrayTubeCurrent = xrayTubeCurrent.value();
if (convolutionKernel.isValid())
dc.convolutionKernel = convolutionKernel.value();
return {}; // If you loaded some additional data e.g. from private tags, you can return it here.
}
void save(const Dicom::FrameDescriptor& frame, const SharedImageSet& sis) const override {}
};
Main specialization of DataComponentBase for regular types.
Definition DataComponent.h:109
T & getOrCreate(Args &&... ctorArgs)
Returns the DataComponent exactly matching the given type.
Definition DataComponentList.h:231
Interface for adding custom functionality to DicomLoader and DicomWriter An Extension might be called...
Definition Extension.h:26
const DataComponentList & components() const
Returns the list of DataComponents for this data.
Definition Data.h:179
T make_unique(T... args)
Mesh * load(const char *buffer, size_t bufferLength, const std::string &sourceFilename="", Progress *progressBar=nullptr, bool log=false)
Loads a mesh from a memory buffer.
void save(const char *filename, const std::vector< mat4 > &matrices, bool rigidPars=false)
Save matrices or its rigid pose parameters to a text file.
Namespace of the ImFusion SDK.
Definition Changelog.dox:1
int sliceIndex
slice index of the frame in the volume (or 0 for 2D images)
Definition DicomIOD.h:60
IOD * iod
Parent IOD that loaded the frame. Never nullptr and must not be re-assigned.
Definition DicomIOD.h:52
int sharedImageIndex
index of the SharedImage in the SharedImageSet
Definition DicomIOD.h:59

In order to use the extension automatically in DicomLoader and DicomWriter, it has to be registered on the IOD_Registry:

static void registerDefaultExtension(std::unique_ptr< const Extension > extension)
Register a Extension that should be available by default.

A good place for registration is the constructor of your ImFusionPlugin.

If your extension also needs to handle enhanced DICOM features, you can structure your code like this:

#include <ImFusion/Base/SharedImageSet.h>
#include <ImFusion/Dicom/DicomElement.h>
#include <ImFusion/Dicom/DicomIOD.h>
#include <ImFusion/Dicom/Extension.h>
#include <dcmtk/dcmdata/dctk.h>
using namespace ImFusion;
class ExtensionExample2 : public Dicom::Extension
{
public:
std::string name() const override { return "Extension Example2"; }
std::vector<std::unique_ptr<Data>> load(const Dicom::FrameDescriptor& frame, SharedImageSet& sis) const override
{
if (auto enhanced = dynamic_cast<Dicom::EnhancedMultiFrameImageIOD*>(frame.iod))
{
// In enhanced DICOM tags can be either shared for a frames in the
// sharedSequence or individual for each frame in the perFrameSequence
bool hasShared = false;
if (enhanced->sharedSequence.count() >= 1)
{
auto& shared = enhanced->sharedSequence.item(0);
Windowing windowing(shared.dataset());
if (windowing.exists())
{
windowing.load(sis);
hasShared = true;
}
}
if (!hasShared && frame.index < enhanced->perFrameSequence.count())
{
auto& perFrame = enhanced->perFrameSequence.item(frame.index);
Windowing windowing(perFrame.dataset());
if (windowing.exists())
windowing.load(sis);
}
}
else
{
Windowing windowing(frame.iod->dataset());
if (windowing.exists())
windowing.load(sis);
}
return {}; // If you loaded some additional data e.g. from private tags, you can return it here.
}
void save(const Dicom::FrameDescriptor& frame, const SharedImageSet& sis) const override {}
private:
// Helper class for using the same code for enhanced- and classic DICOMs
class Windowing
{
public:
Dicom::Element<double> center;
Dicom::Element<double> width;
Windowing(DcmItem& parent)
: center(parent, DCM_WindowCenter, Dicom::ElementBase::Conditional, true)
, width(parent, DCM_WindowWidth, Dicom::ElementBase::Conditional, true)
{
}
bool exists() { return center.exists() && width.exists(); }
void load(SharedImageSet& sis)
{
// ... e.g. set window/center in DisplayOptions2d
}
};
};
DcmDataset & dataset() override
Returns the dataset represented by this IOD.
int index
Index of the frame in the iod.
Definition DicomIOD.h:53

The following example shows how the extension mechanism can be used to load and write private DICOM tags:

#include <ImFusion/Base/DataComponent.h>
#include <ImFusion/Base/SharedImageSet.h>
#include <ImFusion/Dicom/DicomElement.h>
#include <ImFusion/Dicom/DicomIOD.h>
#include <ImFusion/Dicom/Extension.h>
#include <dcmtk/dcmdata/dctk.h>
using namespace ImFusion;
class PrivateTagsDataComponent : public DataComponent<PrivateTagsDataComponent>
{
public:
PrivateTagsDataComponent() = default;
PrivateTagsDataComponent(const PrivateTagsDataComponent& other) = default;
PrivateTagsDataComponent& operator=(const PrivateTagsDataComponent& other) = default;
bool operator==(const PrivateTagsDataComponent& other) const { return other.someString == someString && other.someInt == someInt; }
std::string id() const override { return ""; }
void configure(const Properties* p) override {};
void configuration(Properties* p) const override {};
std::string someString = "";
int someInt = 0;
};
class ExtensionExample3 : public Dicom::Extension
{
public:
ExtensionExample3()
{
// In order to get dcmtk to correctly convert private tags, the tags and their VRs need to be registered with the DcmDataDictionary.
// Here we register the tags with the GlobalDcmDataDictionary "dcmDataDict". This is a global variable defined in dcmtk.
DcmDataDictionary& dict = dcmDataDict.wrlock();
dict.addEntry(
new DcmDictEntry(someStringTag.getGTag(), someStringTag.getETag(), EVR_LO, "PrivateText", 1, 1, "private", OFTrue, creatorName.c_str()));
dict.addEntry(
new DcmDictEntry(someIntTag.getGTag(), someIntTag.getETag(), EVR_US, "PrivateInteger", 1, 1, "private", OFTrue, creatorName.c_str()));
#if OFFIS_DCMTK_VERSION_NUMBER < 364
dcmDataDict.unlock();
#else
dcmDataDict.wrunlock();
#endif
}
std::unique_ptr<Extension> clone() const override { return std::make_unique<ExtensionExample3>(); }
std::string name() const override { return "Extension Example3"; }
std::vector<std::unique_ptr<Data>> load(const Dicom::FrameDescriptor& frame, SharedImageSet& sis) const override
{
if (frame.sharedImageIndex != 0 || frame.sliceIndex != 0)
return {};
// We only create the DataComponent if the tags actually exist and the creator matches
Dicom::Element<std::string> creator(*frame.iod, creatorTag);
// In general any creator can use any odd group number for private tags.
// That means that we need to check first if the creatorName matches
if (creator.isValid() && creator.value() == creatorName)
{
Dicom::Element<std::string> someString(*frame.iod, someStringTag);
Dicom::Element<int> someInt(*frame.iod, someIntTag);
auto& dc = sis.components().getOrCreate<PrivateTagsDataComponent>();
if (someString.isValid())
dc.someString = someString.value();
if (someInt.isValid())
dc.someInt = someInt.value();
}
return {};
}
void save(const Dicom::FrameDescriptor& frame, const SharedImageSet& sis) const override
{
// In this example we only write the private tags if the corresponding DataComponent
// has been added to the SharedImageSet at some point
if (auto dc = sis.components().get<PrivateTagsDataComponent>())
{
Dicom::Element<std::string> creator(*frame.iod, creatorTag);
creator.setValue(creatorName);
Dicom::Element<std::string> someString(*frame.iod, someStringTag);
someString.setValue(dc->someString);
Dicom::Element<int> someInt(*frame.iod, someIntTag);
someInt.setValue(dc->someInt);
}
}
private:
const std::string creatorName = "ImFusion";
const DcmTag creatorTag = DcmTag(DcmTagKey(0x0029, 0x0010), EVR_LO);
const DcmTag someStringTag = DcmTag(DcmTagKey(0x0029, 0x1000), EVR_LO);
const DcmTag someIntTag = DcmTag(DcmTagKey(0x0029, 0x1010), EVR_US);
};
T * get()
Returns the DataComponent exactly matching the given type.
Definition DataComponentList.h:210

For details on private DICOM tags please consider this part of the standard and this example for dcmtk on which our example is build.

Classes

class  ImFusion::Dicom::EncryptedAttributesExtension
class  ImFusion::Dicom::Extension
 Interface for adding custom functionality to DicomLoader and DicomWriter An Extension might be called in a thread that is not the main thread. More...
class  ImFusion::Dicom::GeneralEquipmentExtension
 General Equipment Module (PS 3.3 C.7.5) Only considers tags that are part of the GeneralEquipmentModuleDataComponent. More...
class  ImFusion::Dicom::RealWorldValueMappingExtension
 Real World Value Mapping Macro (PS 3.3 C.7.6.16.2.11). More...
class  ImFusion::Dicom::VOILUTExtension
 VOI LUT Module (PS 3.3 C.11.2) Loads and saves windowing related values to and from DisplayOptions2d. More...
Search Tab / S to search, Esc to close