ImFusion SDK 4.3
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 Assert.h:7
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
int sliceIndex
slice index of the frame in the volume (or 0 for 2D images)
Definition DicomIOD.h:60

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
}
};
};
virtual bool exists() const
Returns true if the elements exists in the dataset.
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  EncryptedAttributesExtension
 
class  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  GeneralEquipmentExtension
 General Equipment Module (PS 3.3 C.7.5) Only considers tags that are part of the GeneralEquipmentModuleDataComponent. More...
 
class  RealWorldValueMappingExtension
 Real World Value Mapping Macro (PS 3.3 C.7.6.16.2.11) More...
 
class  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