⚠️ Ceci est un site de traduction non officiel, sans lien avec DCMTK / OFFIS. Pour des informations faisant autorité, consultez la page originale (https://support.dcmtk.org/docs/mod_dcmpmap.html).

dcmpmap: une bibliothèque pour manipuler les objets Parametric Map

Ce module contient les classes permettant de créer, charger, consulter et enregistrer des objets DICOM Parametric Map, qui ont été introduits à l'origine dans la norme DICOM par le Supplement 172 en 2014.

Dans la norme, les données contenues dans chaque objet Parametric Map doivent reposer sur l'un des types de données suivants :

  • entier non signé 16 bits
  • entier signé 16 bits
  • virgule flottante 32 bits
  • virgule flottante 64 bits

Tous sont pris en charge par la bibliothèque dcmpmap.

La classe principale de ce module est :

Ce module fait largement appel au module dcmiod pour la gestion des attributs IOD communs, et au module dcmfg pour la prise en charge des groupes fonctionnels (functional group). Consultez les sections "Exemples" pour plus de détails.

Exemples

Les deux exemples suivants montrent :

  • comment accéder aux informations (y compris les valeurs de données binaires) d'un objet Parametric Map et les extraire
  • et comment utiliser l'API pour créer soi-même un tel objet.

Extraction d'informations depuis un Parametric Map

La classe Parametric Map utilise un template pour instancier en interne le type de données de pixel correct et proposer une API dédiée à ce type. Les types autorisés sont Uint16, Sint16, Float32 et Float64.

En interne, les types de données sont gérés via un Variant C++, si bien que la technique habituelle pour "basculer" entre ces types dans le code consiste à utiliser un Visitor qui surcharge l'opérateur "()" pour chaque type de données pouvant apparaître dans le Variant. Ce principe est également illustré ci-dessous, où le type de données de pixel est affiché.

Le reste du code utilise l'API des modules dcmiod et dcmfg pour obtenir les informations de base sur Patient, Study, Series et Instance, ainsi que les informations de groupe fonctionnel, en particulier le Real World Value Mapping défini dans le fichier.

#include "dcmtk/config/osconfig.h" / make sure OS specific configuration is included first /

#include "dcmtk/dcmpmap/dpmparametricmapiod.h"

static void dumpRWVM(const unsigned long frameNumber,

FGInterface& fg)

{

FGRealWorldValueMapping rw = OFstatic_cast(FGRealWorldValueMapping, fg.get(frameNumber, DcmFGTypes::EFG_REALWORLDVALUEMAPPING));

if (rw)

{

size_t numMappings = rw->getRealWorldValueMapping().size();

COUT << " Number of Real World Value Mappings defined: " << numMappings << OFendl;

for (size_t m = 0; m < numMappings; m++)

{

FGRealWorldValueMapping::RWVMItem* item = rw->getRealWorldValueMapping()[m];

OFString label, expl;

item->getLUTLabel(label);

item->getLUTExplanation(expl);

COUT << " RWVM Mapping #" << m << ":" << OFendl;

COUT << " LUT Label: << " << label << OFendl;

COUT << " LUT Explanation: " << expl << OFendl;

COUT << " Measurement Units Code: " << item->getMeasurementUnitsCode().toString() << OFendl;

size_t numQuant = item->getEntireQuantityDefinitionSequence().size();

if (numQuant > 0)

{

COUT << " Number of Quantities defined: " << numQuant << OFendl;

for (size_t q = 0; q < numQuant; q++)

{

ContentItemMacro* macro = item->getEntireQuantityDefinitionSequence()[q];

COUT << " Quantity #" << q << ": " << macro->toString() << OFendl;

}

}

}

}

else

{

CERR << " Error: No Real World Value Mappings defined for frame #" << frameNumber << OFendl;

}

}

class DumpFramesVisitor

{

public:

DumpFramesVisitor(DPMParametricMapIOD* map,

const unsigned long numPerFrame)

m_Map(map)

, m_numPerFrame(numPerFrame)

{

}

template

OFBool operator()(DPMParametricMapIOD::Frames& frames)

{

dumpDataType(frames);

for (unsigned long f = 0; f < m_Map->getNumberOfFrames(); f++)

{

COUT << "Dumping info of frame #" << f << ":" << OFendl;

FGInterface& fg = m_Map->getFunctionalGroups();

dumpRWVM(f, fg);

COUT << "Dumping data for frame #" << f << ": " << OFendl;

T* frame = frames.getFrame(f);

for (unsigned long p = 0; p < m_numPerFrame; p++)

{

COUT << frame[p] << " ";

}

COUT << OFendl << OFendl;

}

return 0;

}

OFBool operator()(OFCondition& cond)

{

// Avoid compiler warning

(void)cond;

CERR << "Type d'échantillons de données non pris en charge" << OFendl;

return OFFalse;

}

OFBool dumpHeader(DPMParametricMapIOD::Frames& frames)

{

// Avoid compiler warning

(void)frames;

COUT << "File has 32 Bit float data" << OFendl;

return OFFalse;

}

OFBool dumpHeader(DPMParametricMapIOD::Frames& frames)

{

// Avoid compiler warning

(void)frames;

COUT << "File has 16 Bit unsigned integer data" << OFendl;

return OFFalse;

}

OFBool dumpHeader(DPMParametricMapIOD::Frames& frames)

{

// Avoid compiler warning

(void)frames;

COUT << "File has 16 Bit signed integer data" << OFendl;

return OFFalse;

}

OFBool dumpHeader(DPMParametricMapIOD::Frames& frames)

{

// Avoid compiler warning

(void)frames;

COUT << "File has 64 Bit float data" << OFendl;

return OFTrue;

}

template

OFBool dumpDataType(DPMParametricMapIOD::Frames& frames)

{

// Avoid compiler warning

(void)frames;

CERR << "Type d'échantillons de données non pris en charge" << OFendl;

return OFFalse;

}

DPMParametricMapIOD* m_Map;

unsigned long m_numPerFrame;

};

static void dumpGeneral(DPMParametricMapIOD& map)

{

OFString patName, patID, studyUID, studyDate, seriesUID, modality, sopUID;

map.getPatient().getPatientName(patName);

map.getPatient().getPatientID(patID);

map.getStudy().getStudyInstanceUID(studyUID);

map.getStudy().getStudyDate(studyDate);

map.getSeries().getSeriesInstanceUID(seriesUID);

map.getSeries().getModality(modality);

map.getSOPCommon().getSOPInstanceUID(sopUID);

COUT << "Patient Name : " << patName << OFendl;

COUT << "Patient ID : " << patID << OFendl;

COUT << "Study Instance UID : " << studyUID << OFendl;

COUT << "Study Date : " << studyDate << OFendl;

COUT << "Series Instance UID: " << seriesUID << OFendl;

COUT << "SOP Instance UID : " << sopUID << OFendl;

COUT << "---------------------------------------------------------------" << OFendl;

OFBool isPerFrame;

map.getFunctionalGroups().get(0, DcmFGTypes::EFG_REALWORLDVALUEMAPPING, isPerFrame);

if (isPerFrame)

{

COUT << "Real World Value Mapping: Defined per-frame" << OFendl;

}

else

{

COUT << "Real World Value Mapping: Defined shared (i.e. single definition for all frames):" << OFendl;

}

COUT << "---------------------------------------------------------------" << OFendl;

}

int main (int argc, char* argv[])

{

// OFLog::configure(OFLogger::DEBUG_LOG_LEVEL);

OFString inputFile;

if (argc < 2)

{

CERR << "Usage: dump_pmp " << std::endl;

return 1;

}

else

{

inputFile = argv[1];

if (OFStandard::fileExists(inputFile))

{

CERR << "Input file " << inputFile << " does not exist " << OFendl;

return 1;

}

}

OFvariant result = DPMParametricMapIOD::loadFile(inputFile);

if (OFget(&result))

{

DPMParametricMapIOD map = OFget(&result);

dumpGeneral(*map);

COUT << "Dumping #" << map->getNumberOfFrames() << " frames of file " << inputFile << OFendl;

Uint16 rows, cols = 0;

map->getRows(rows);

map->getColumns(cols);

unsigned long numPerFrame = rows * cols;

DPMParametricMapIOD::FramesType frames = map->getFrames();

OFvisit(DumpFramesVisitor(map, numPerFrame), frames);

}

else

{

CERR << "Could not load parametric map: " << (*OFget(&result)).text() << OFendl;

exit(1);

}

exit(0);

}

Création de Parametric Map

La classe Parametric Map utilise un template pour instancier en interne le type de données de pixel correct et proposer une API dédiée à ce type. Les types autorisés sont Uint16, Sint16, Float32 et Float64. L'exemple ci-dessous montre que l'utilisation de l'API est globalement identique pour tous les types.

La procédure suivie dans l'exemple (dont la majeure partie s'applique également au cas général) est la suivante :

  • La routine main() appelle test_pmap() quatre fois, en utilisant à chaque fois un Image Pixel Module différent comme paramètre de template, ce qui garantit que le bon type de données de pixel est utilisé dans le Parametric Map créé.
  • test_pmap() illustre l'ensemble des étapes de création de la carte :
  • Créer un nouveau Parametric Map en appelant DPMParametricMapIOD::create() (via create_pmap()), puis
  • ajouter les groupes fonctionnels partagés,
  • ajouter des dimensions,
  • et ajouter à l'objet les frames avec les groupes fonctionnels par frame associés.
  • Enfin, les données générales relatives à Patient et Study sont définies.
  • Notez que l'ordre de ces étapes dans test_pmap() n'a pas d'importance.

Par défaut, DPMParametricMapIOD::create() crée une nouvelle instance DICOM, au sein d'une toute nouvelle Series DICOM appartenant elle-même à une toute nouvelle Study DICOM. Toutes les informations minimales pour Patient, Study et Series sont définies (par exemple Study, Series et SOP Instance UID, ainsi que d'autres informations transmises à l'appel de create(), comme Series Number). Patient Name et ID restent vides par défaut.

Bien sûr, il arrive souvent que vous vouliez plutôt placer la nouvelle instance dans une Series existante, ou placer la toute nouvelle Series dans une Study existante, ou au moins l'affecter à un Patient existant. Le moyen le plus simple d'y parvenir est d'utiliser l'appel import(), qui importe les informations de Patient, voire de Study, Series et Frame of Reference, à partir d'un fichier existant, c'est-à-dire qu'il place l'instance sous un Patient, une Study et/ou une Series existants.

Lorsque des informations sont ajoutées au Parametric Map via l'API publique, quelques vérifications de base sont généralement effectuées sur les données. Enfin, lors de l'appel à saveFile(), des vérifications supplémentaires ont lieu, par exemple la validation de la structure des groupes fonctionnels ou la confirmation que toutes les valeurs d'éléments requises sont définies.

#include "dcmtk/config/osconfig.h" / make sure OS specific configuration is included first /

#include "dcmtk/ofstd/oftest.h"

#include "dcmtk/dcmiod/iodutil.h"

#include "dcmtk/dcmpmap/dpmparametricmapiod.h"

#include "dcmtk/dcmfg/fgpixmsr.h"

#include "dcmtk/dcmfg/fgplanpo.h"

#include "dcmtk/dcmfg/fgplanor.h"

#include "dcmtk/dcmfg/fgfracon.h"

#include "dcmtk/dcmfg/fgframeanatomy.h"

#include "dcmtk/dcmfg/fgidentpixeltransform.h"

#include "dcmtk/dcmfg/fgframevoilut.h"

#include "dcmtk/dcmfg/fgrealworldvaluemapping.h"

#include "dcmtk/dcmfg/fgparametricmapframetype.h"

const size_t NUM_FRAMES = 10;

const Uint16 ROWS = 10;

const Uint16 COLS = 10;

const unsigned long NUM_VALUES_PER_FRAME = ROWS * COLS;

// Set Patient and Study example data

static void setGenericData(DPMParametricMapIOD& map)

{

map.getPatient().setPatientName("Onken^Michael");

map.getPatient().setPatientID("007");

map.getStudy().setStudyDate("20160721");

map.getStudy().setStudyTime("111200");

map.getStudy().setStudyID("4711");

}

// Create Parametric Map

template

static OFvariant create_pmap()

{

return DPMParametricMapIOD::create

(

"MR", // Modality

"1", // Series Number

"1", // Instance Number

ROWS,

COLS,

IODEnhGeneralEquipmentModule::EquipmentInfo("Open Connections GmbH", "make_pmp", "SN_0815", "0.1"),

ContentIdentificationMacro("1", "PARAMAP_LABEL", "Example description from test program", "Onken^Michael"),

"VOLUME", // Image Flavor

"MTT", // Derived Pixel Contrast

DPMTypes::CQ_RESEARCH // Content Qualification

);

}

// Add those functional groups that are common for all frames

static OFCondition addSharedFunctionalGroups(DPMParametricMapIOD& map)

{

FGPixelMeasures pixelMeasures;

pixelMeasures.setPixelSpacing("1\\1");

pixelMeasures.setSliceThickness("0.1");

pixelMeasures.setSpacingBetweenSlices("0.1");

FGPlaneOrientationPatient planeOrientPatientFG;

planeOrientPatientFG.setImageOrientationPatient("1", "0", "0", "0", "1", "0");

FGFrameAnatomy frameAnaFG;

frameAnaFG.setLaterality(FGFrameAnatomy::LATERALITY_UNPAIRED);

frameAnaFG.getAnatomy().getAnatomicRegion().set("T-A0100", "SRT", "Brain");

FGIdentityPixelValueTransformation idTransFG;

FGParametricMapFrameType frameTypeFG;

frameTypeFG.setFrameType("DERIVED\\PRIMARY\\VOLUME\\MTT");

// Add groups to Parametric Map

OFCondition result;

if ((result = map.addForAllFrames(pixelMeasures)).good())

if ((result = map.addForAllFrames(planeOrientPatientFG)).good())

if ((result = map.addForAllFrames(frameAnaFG)).good())

if ((result = map.addForAllFrames(idTransFG)).good())

result = map.addForAllFrames(frameTypeFG);

return result;

}

// Add a single dimension for demonstration purposes based on "Image Position Patient"

static OFCondition addDimensions(DPMParametricMapIOD& map)

{

IODMultiframeDimensionModule& mod = map.getIODMultiframeDimensionModule();

OFString dimUID = DcmIODUtil::createUID(0);

OFCondition result = mod.addDimensionIndex(DCM_ImagePositionPatient, dimUID, DCM_RealWorldValueMappingSequence, "Frame position");

return result;

}

// Add one frame to parametric map. Frame number is used to compute some

// varying example data values differing from frame to frame

template

static OFCondition addFrame(DPMParametricMapIOD& map,

const unsigned long frameNo)

{

// Create example data

OFVector data(NUM_VALUES_PER_FRAME);

for (size_t n=0; n < data.size(); ++n)

{

data[n] = (n*frameNo+n) + (0.1 * (frameNo % 10));

}

Uint16 rows, cols;

OFCondition cond = map.getImagePixel().getRows(rows);

cond = map.getImagePixel().getColumns(cols);

// Create functional groups

OFVector groups;

OFunique_ptr fgPlanePos(new FGPlanePosPatient);

OFunique_ptr fgFracon(new FGFrameContent);

OFunique_ptr fgRVWM(new FGRealWorldValueMapping());

FGRealWorldValueMapping::RWVMItem* rvwmItemSimple = new FGRealWorldValueMapping::RWVMItem();

if (!fgPlanePos || !fgFracon || !fgRVWM || !rvwmItemSimple )

return EC_MemoryExhausted;

// Fill in functional group values

// Real World Value Mapping

rvwmItemSimple->setRealWorldValueSlope(10);

rvwmItemSimple->setRealWorldValueIntercept(0);

rvwmItemSimple->setDoubleFloatRealWorldValueFirstValueMapped(0.12345);

rvwmItemSimple->setDoubleFloatRealWorldValueLastValueMapped(98.7654);

rvwmItemSimple->getMeasurementUnitsCode().set("{counts}/s", "UCUM", "Counts per second");

rvwmItemSimple->setLUTExplanation("We are mapping trash to junk.");

rvwmItemSimple->setLUTLabel("Just testing");

CodeSequenceMacro* qCodeName = new CodeSequenceMacro("G-C1C6", "SRT", "Quantity");

CodeSequenceMacro* qSpec = new CodeSequenceMacro("110805", "SRT", "T2 Weighted MR Signal Intensity");

ContentItemMacro* quantity = new ContentItemMacro;

if (!quantity || !qSpec || !quantity)

return EC_MemoryExhausted;

quantity->getEntireConceptNameCodeSequence().push_back(qCodeName);

quantity->getEntireConceptCodeSequence().push_back(qSpec);

rvwmItemSimple->getEntireQuantityDefinitionSequence().push_back(quantity);

quantity->setValueType(ContentItemMacro::VT_CODE);

fgRVWM->getRealWorldValueMapping().push_back(rvwmItemSimple);

// Plane Position

OFStringStream ss;

ss << frameNo;

OFSTRINGSTREAM_GETOFSTRING(ss, framestr) // convert number to string

fgPlanePos->setImagePositionPatient("0", "0", framestr);

// Frame Content

OFCondition result = fgFracon->setDimensionIndexValues(frameNo+1 / value within dimension /, 0 / first dimension /);

// Add frame with related groups

if (result.good())

{

// Add frame

groups.push_back(fgPlanePos.get());

groups.push_back(fgFracon.get());

groups.push_back(fgRVWM.get());

groups.push_back(fgPlanePos.get());

DPMParametricMapIOD::FramesType frames = map.getFrames();

result = OFget >(&frames)->addFrame(&*data.begin(), NUM_VALUES_PER_FRAME, groups);

}

return result;

}

// Main routine that creates Parametric Maps

template

static OFCondition test_pmap(const OFString& saveDestination)

{

OFvariant obj = create_pmap();

if (OFCondition* pCondition = OFget(&obj))

return *pCondition;

DPMParametricMapIOD& map = *OFget(&obj);

OFCondition result;

if ((result = addSharedFunctionalGroups(map)).good())

if ((result = addDimensions(map)).good())

{

// Add frames (parametric map data), and per-frame functional groups

for (unsigned long f = 0; result.good() && (f < NUM_FRAMES); f++)

result = addFrame(map, f);

}

// Set some generic data (keep dciodvfy happy on DICOMDIR warnings)

if (result.good())

{

setGenericData(map);

}

// Save

if (result.good())

{

return map.saveFile(saveDestination.c_str());

}

else

{

return result;

}

}

int main (int argc, char* argv[])

{

OFString outputDir;

if (argc < 2)

{

CERR << "Usage: make_pmp " << std::endl;

return 1;

}

else

{

outputDir = argv[1];

if (OFStandard::dirExists(outputDir))

{

CERR << "Output directory " << outputDir << " does not exist " << OFendl;

return 1;

}

}

//OFLog::configure(OFLogger::DEBUG_LOG_LEVEL);

// Test all possible parametric map types (signed and unsigned integer, floating point

// and double floating point)

test_pmap >(outputDir + "/uint_paramap.dcm");

test_pmap >(outputDir + "/sint_paramap.dcm");

test_pmap(outputDir + "/float_paramap.dcm");

test_pmap(outputDir + "/double_paramap.dcm");

return 0;

}