⚠️ これは 非公式の翻訳サイトです。DCMTK / OFFIS とは無関係です。正確な情報は 原文(https://support.dcmtk.org/docs/mod_dcmpmap.html) を参照してください。

dcmpmap: パラメトリックマップオブジェクトを扱うためのライブラリ

このモジュールには、DICOM Parametric Map オブジェクトを作成・読み込み・アクセス・保存するためのクラスが含まれます。Parametric Map オブジェクトは 2014 年に Supplement 172 によって DICOM 規格へ導入されたものです。

規格上、各 Parametric Map オブジェクト内のデータは次のいずれかのデータ型でなければなりません:

  • 16 ビット符号なし整数
  • 16 ビット符号付き整数
  • 32 ビット浮動小数点
  • 64 ビット浮動小数点

これらはすべて dcmpmap ライブラリでサポートされています。

このモジュールの主要なクラスは次のとおりです:

このモジュールは、共通の IOD 属性を管理するために dcmiod モジュールを、機能グループ(functional group)のサポートのために dcmfg モジュールを多用します。詳しい説明は「例」の節を参照してください。

次の 2 つの例では以下を示します:

  • Parametric Map オブジェクトから情報(バイナリのデータ値を含む)にアクセスしてダンプする方法
  • そして API を使い、そのようなオブジェクトを自分で作成する方法。

Parametric Map からの情報のダンプ

Parametric Map クラスはテンプレートを使い、内部で正しいピクセルデータ型をインスタンス化し、その型専用の API を提供します。指定できる型は Uint16、Sint16、Float32、Float64 です。

内部ではデータ型は C++ の Variant で扱われるため、コード中でこれらの型を「切り替える」一般的な手法は、Variant に現れうる各データ型に対して operator "()" をオーバーロードした Visitor を使うことです。この手法は、ピクセルデータの型を出力する以下のコードでも示されています。

残りのコードは、dcmioddcmfg モジュールの API を使い、Patient・Study・Series・Instance に関する基本情報のほか、機能グループの情報、特にファイルに定義された Real World Value Mapping を取得します。

#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 of data samples not supported" << 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 of data samples not supported" << 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);

}

Parametric Map の作成

Parametric Map クラスはテンプレートを使い、内部で正しいピクセルデータ型をインスタンス化し、その型専用の API を提供します。指定できる型は Uint16、Sint16、Float32、Float64 です。以下の例は、API の使い方がどの型でも基本的に同じであることを示しています。

例における手順(その大半は一般的な場合にも当てはまります)は次のとおりです:

  • main() ルーチンは test_pmap() を 4 回呼び出します。毎回、異なる Image Pixel Module をテンプレート引数として使い、作成される Parametric Map 内で正しいピクセルデータ型が使われるようにします。
  • test_pmap() はマップを作成する全体的な手順を示します:
  • DPMParametricMapIOD::create() を(create_pmap() 経由で)呼び出して新しい Parametric Map を作成し、続いて
  • 共有の機能グループを追加し、
  • 次元(dimension)を追加し、
  • 関連するフレームごとの機能グループとともにフレームをオブジェクトへ追加します。
  • 最後に、Patient と Study に関する一般的なデータを設定します。
  • test_pmap() におけるこれらの手順の順序は問わない点に注意してください。

既定では、DPMParametricMapIOD::create() は、まったく新しい DICOM Study に属するまったく新しい DICOM Series の中に、新しい DICOM インスタンスを作成します。Patient・Study・Series に必要な最小限の情報がすべて設定されます(例えば Study・Series・SOP Instance UID のほか、Series Number など create() 呼び出しに渡されたその他の情報)。Patient Name と ID は既定では空のままになります。

もちろん、新しいインスタンスを既存の Series に入れたい場合や、まったく新しい Series を既存の Study に置きたい、あるいは少なくとも既存の Patient に割り当てたい場合も多いでしょう。それを行う最も簡単な方法は、import() 呼び出しを使うことです。これは既存のファイルから Patient、さらには Study・Series・Frame of Reference の情報を取り込み、つまり既存の Patient・Study・Series の下に配置するものです。

公開 API を使って Parametric Map に情報を追加する際は、通常データに対していくつかの基本的なチェックが行われます。最後に saveFile() を呼び出すと、機能グループの構造を検証したり、必要なすべての要素値が設定されているかを確認したりと、さらにいくつかのチェックが実行されます。

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

}