⚠️ これは 非公式の翻訳サイトです。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 を使うことです。この手法は、ピクセルデータの型を出力する以下のコードでも示されています。

残りのコードは、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<typename T>
  OFBool operator()(DPMParametricMapIOD::Frames<T>& 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<Float32>& frames)
  {
 // Avoid compiler warning
    (void)frames;
    COUT << "File has 32 Bit float data" << OFendl;
 return OFFalse;
  }

  OFBool dumpHeader(DPMParametricMapIOD::Frames<Uint16>& frames)
  {
 // Avoid compiler warning
    (void)frames;
    COUT << "File has 16 Bit unsigned integer data" << OFendl;
 return OFFalse;
  }

  OFBool dumpHeader(DPMParametricMapIOD::Frames<Sint16>& frames)
  {
 // Avoid compiler warning
    (void)frames;
    COUT << "File has 16 Bit signed integer data" << OFendl;
 return OFFalse;
  }

  OFBool dumpHeader(DPMParametricMapIOD::Frames<Float64>& frames)
  {
 // Avoid compiler warning
    (void)frames;
    COUT << "File has 64 Bit float data" << OFendl;
 return OFTrue;
  }

 template<typename T>
  OFBool dumpDataType(DPMParametricMapIOD::Frames<T>& 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 <input-file>" << std::endl;
 return 1;
  }
 else
  {
    inputFile = argv[1];
 if (!OFStandard::fileExists(inputFile))
    {
      CERR << "Input file " << inputFile << " does not exist " << OFendl;
 return 1;
    }
  }

 OFvariant<OFCondition,DPMParametricMapIOD*> result = DPMParametricMapIOD::loadFile(inputFile);
 if (OFget<DPMParametricMapIOD*>(&result))
  {
 DPMParametricMapIOD* map = *OFget<DPMParametricMapIOD*>(&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<OFBool>(DumpFramesVisitor(map, numPerFrame), frames);
  }
 else
  {
    CERR << "Could not load parametric map: " << (*OFget<OFCondition>(&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<typename ImagePixel>
static OFvariant<OFCondition,DPMParametricMapIOD> create_pmap()
{
 return DPMParametricMapIOD::create<ImagePixel>
  (
 "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 <typename PixelType>
static OFCondition addFrame(DPMParametricMapIOD& map,
 const unsigned long frameNo)
{
 // Create example data

 OFVector<PixelType> 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<FGBase*> groups;
 OFunique_ptr<FGPlanePosPatient> fgPlanePos(new FGPlanePosPatient);
 OFunique_ptr<FGFrameContent > fgFracon(new FGFrameContent);
 OFunique_ptr<FGRealWorldValueMapping> 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<DPMParametricMapIOD::Frames<PixelType> >(&frames)->addFrame(&*data.begin(), NUM_VALUES_PER_FRAME, groups);
  }

 return result;
}


// Main routine that creates Parametric Maps
template<typename ImagePixel>
static OFCondition test_pmap(const OFString& saveDestination)
{
 OFvariant<OFCondition,DPMParametricMapIOD> obj = create_pmap<ImagePixel>();
 if (OFCondition* pCondition = OFget<OFCondition>(&obj))
 return *pCondition;

 DPMParametricMapIOD& map = *OFget<DPMParametricMapIOD>(&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<OFTypename ImagePixel::value_type>(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 <output-dir>" << 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<IODImagePixelModule<Uint16> >(outputDir + "/uint_paramap.dcm");
  test_pmap<IODImagePixelModule<Sint16> >(outputDir + "/sint_paramap.dcm");
  test_pmap<IODFloatingPointImagePixelModule>(outputDir + "/float_paramap.dcm");
  test_pmap<IODDoubleFloatingPointImagePixelModule>(outputDir + "/double_paramap.dcm");

 return 0;
}