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 を使うことです。この手法は、ピクセルデータの型を出力する以下のコードでも示されています。
残りのコードは、dcmiod と dcmfg モジュールの 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
{
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
{
// Avoid compiler warning
(void)frames;
COUT << "File has 32 Bit float data" << OFendl;
return OFFalse;
}
OFBool dumpHeader(DPMParametricMapIOD::Frames
{
// Avoid compiler warning
(void)frames;
COUT << "File has 16 Bit unsigned integer data" << OFendl;
return OFFalse;
}
OFBool dumpHeader(DPMParametricMapIOD::Frames
{
// Avoid compiler warning
(void)frames;
COUT << "File has 16 Bit signed integer data" << OFendl;
return OFFalse;
}
OFBool dumpHeader(DPMParametricMapIOD::Frames
{
// Avoid compiler warning
(void)frames;
COUT << "File has 64 Bit float data" << OFendl;
return OFTrue;
}
template
OFBool dumpDataType(DPMParametricMapIOD::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
return 1;
}
else
{
inputFile = argv[1];
if ((inputFile))
{
CERR << "Input file " << inputFile << " does not exist " << OFendl;
return 1;
}
}
OFvariant
if (OFget
{
DPMParametricMapIOD map = OFget
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
}
else
{
CERR << "Could not load parametric map: " << (*OFget
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
{
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
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
OFunique_ptr
OFunique_ptr
OFunique_ptr
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
}
return result;
}
// Main routine that creates Parametric Maps
template
static OFCondition test_pmap(const OFString& saveDestination)
{
OFvariant
if (OFCondition* pCondition = OFget
return *pCondition;
DPMParametricMapIOD& map = *OFget
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
}
// 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
return 1;
}
else
{
outputDir = argv[1];
if ((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
test_pmap
test_pmap
test_pmap
return 0;
}