1 /*
2  *
3  *  Copyright (C) 2019-2020, OFFIS e.V.
4  *  All rights reserved.  See COPYRIGHT file for details.
5  *
6  *  This software and supporting documentation were developed by
7  *
8  *    OFFIS e.V.
9  *    R&D Division Health
10  *    Escherweg 2
11  *    D-26121 Oldenburg, Germany
12  *
13  *
14  *  Module:  dcmect
15  *
16  *  Author:  Michael Onken
17  *
18  *  Purpose: Tests for creating and loading Enhanced CT objects
19  *
20  */
21 
22 #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */
23 
24 #include "dcmtk/ofstd/ofmem.h"
25 #include "dcmtk/ofstd/ofstrutl.h"
26 #include "dcmtk/ofstd/oftempf.h"
27 #include "dcmtk/ofstd/oftest.h"
28 
29 #include "dcmtk/dcmect/enhanced_ct.h"
30 
31 #include "dcmtk/dcmfg/concatenationcreator.h"
32 #include "dcmtk/dcmfg/concatenationloader.h"
33 #include "dcmtk/dcmfg/fgctacquisitiondetails.h"
34 #include "dcmtk/dcmfg/fgctacquisitiontype.h"
35 #include "dcmtk/dcmfg/fgctadditionalxraysource.h"
36 #include "dcmtk/dcmfg/fgctexposure.h"
37 #include "dcmtk/dcmfg/fgctgeometry.h"
38 #include "dcmtk/dcmfg/fgctimageframetype.h"
39 #include "dcmtk/dcmfg/fgctposition.h"
40 #include "dcmtk/dcmfg/fgctreconstruction.h"
41 #include "dcmtk/dcmfg/fgcttabledynamics.h"
42 #include "dcmtk/dcmfg/fgctxraydetails.h"
43 #include "dcmtk/dcmfg/fgfracon.h"
44 #include "dcmtk/dcmfg/fgframeanatomy.h"
45 #include "dcmtk/dcmfg/fgframevoilut.h"
46 #include "dcmtk/dcmfg/fgirradiationeventid.h"
47 #include "dcmtk/dcmfg/fgpixeltransform.h"
48 #include "dcmtk/dcmfg/fgpixmsr.h"
49 #include "dcmtk/dcmfg/fgplanor.h"
50 #include "dcmtk/dcmfg/fgplanpo.h"
51 #include "dcmtk/dcmfg/fgrealworldvaluemapping.h"
52 #include "dcmtk/dcmfg/fgtemporalposition.h"
53 
54 static OFLogger tRoundLogger = OFLog::getLogger("dcmtk.test.t_roundtrip");
55 
56 // Do not change values below since
57 // a) The expected dataset (dump) is made for these values
58 // b) Total pixel data length in this test must not be bigger than
59 //    4 GB, otherwise the calls to writeDataset() will fail.
60 // The test dcmect/tests/t_huge_concat.cc allows for exercising
61 // "unlimited" pixel data size using writeConcatenation() on all
62 // occassions.
63 
64 static const Uint16 NUM_ROWS             = 2;
65 static const Uint16 NUM_COLS             = 2;
66 static const Uint16 NUM_FRAMES           = 2;
67 static const size_t NUM_PIXELS_PER_FRAME = NUM_COLS * NUM_ROWS;
68 static const size_t NUM_FRAMES_CONCAT    = 1;
69 
70 static OFString EXPECTED_DUMP;
71 
72 static void prepareExpectedDump();
73 static EctEnhancedCT* create();
74 static void configureIOD(EctEnhancedCT* ct);
75 static void setGenericValues(EctEnhancedCT* ct);
76 static void addSharedFGs(EctEnhancedCT* ct);
77 static void addFrames(EctEnhancedCT* ct);
78 static void addDimensions(EctEnhancedCT* ct);
79 static OFString write(EctEnhancedCT* ct, DcmDataset& ds);
80 static void checkCreatedObject(const OFString& ds_dump);
81 static void writeAndCheckConcatenation(EctEnhancedCT* ct, OFList<OFFilename>& concats);
82 static void checkConcatenationInstance(size_t numInstance, EctEnhancedCT* srcInstance, DcmDataset* concatInstance);
83 static void loadAndCheckConcatenation(const OFList<OFFilename>& concats);
84 
OFTEST(dcmect_roundtrip)85 OFTEST(dcmect_roundtrip)
86 {
87     /* make sure data dictionary is loaded */
88     if (!dcmDataDict.isDictionaryLoaded())
89     {
90         OFCHECK(dcmDataDict.isDictionaryLoaded());
91         return;
92     }
93 
94     // Creation
95     EctEnhancedCT* ct = create();
96     configureIOD(ct);
97     setGenericValues(ct);
98     addSharedFGs(ct);
99     addFrames(ct);
100     addDimensions(ct);
101 
102     // Write to dataset and compare its dump with expected result
103     DcmFileFormat dcmff;
104     DcmDataset* ds = dcmff.getDataset();
105     prepareExpectedDump();
106     OFString dset_dump = write(ct, *ds);
107     checkCreatedObject(dset_dump);
108 
109     // Save to disk, and re-load to test import
110     OFTempFile tf;
111     OFString temp_fn = tf.getFilename();
112     OFCHECK(!temp_fn.empty());
113     OFCHECK(ct->saveFile(temp_fn.c_str(), EXS_LittleEndianExplicit).good());
114 
115     // Read object from dataset into EctEnhancedCT object, write again to dataset and
116     // check whether daset before saving to file is identical to object after loading from
117     // disk and writing to dataset
118     delete ct;
119     ct = NULL;
120     ds->clear();
121     OFCHECK(EctEnhancedCT::loadFile(temp_fn, ct).good());
122     OFCHECK(ct != NULL);
123     if (ct)
124     {
125         dset_dump = write(ct, *ds);
126         checkCreatedObject(dset_dump);
127     }
128     OFList<OFFilename> concats;
129     if (ct)
130     {
131         writeAndCheckConcatenation(ct, concats);
132         delete ct;
133     }
134     OFCHECK(!concats.empty());
135     if (!concats.empty())
136     {
137         loadAndCheckConcatenation(concats);
138     }
139     OFListIterator(OFFilename) it = concats.begin();
140     while (it != concats.end())
141     {
142         OFStandard::deleteFile(*it);
143         it++;
144     }
145 }
146 
create()147 static EctEnhancedCT* create()
148 {
149     IODEnhGeneralEquipmentModule::EquipmentInfo eq("Open Connections", "OC CT", "4711", "0.1");
150     EctEnhancedCT* ct = NULL;
151     OFCondition result;
152     result = EctEnhancedCT::create(ct,
153                                    NUM_ROWS,
154                                    NUM_COLS,
155                                    OFFalse,
156                                    EctTypes::E_ImageType1_Original,
157                                    EctTypes::DT_ImageType3_Volume,
158                                    EctTypes::DT_ImageType4_Maximum,
159                                    "1" /* instance number */,
160                                    EctTypes::E_ContQuali_Research,
161                                    EctTypes::E_PixelPres_Monochrome,
162                                    EctTypes::E_VolProps_Volume,
163                                    EctTypes::DT_VolBasedCalcTechnique_VolumeRender,
164                                    eq,
165                                    "20190801120000" /* acquisition date */,
166                                    2.0 /* acquisition duration */);
167 
168     OFCHECK(result.good());
169     OFCHECK(ct != OFnullptr);
170     return ct;
171 }
172 
configureIOD(EctEnhancedCT * ct)173 static void configureIOD(EctEnhancedCT* ct)
174 {
175     if (!ct)
176         return;
177 }
178 
setGenericValues(EctEnhancedCT * ct)179 static void setGenericValues(EctEnhancedCT* ct)
180 {
181     if (!ct)
182         return;
183     OFCHECK(ct->getPatient().setPatientName("Bond^James").good());
184     OFCHECK(ct->getPatient().setPatientID("007").good());
185     OFCHECK(ct->getPatient().setPatientBirthDate("19771007").good());
186     OFCHECK(ct->getStudy().setStudyDate("20190801").good());
187     OFCHECK(ct->getStudy().setStudyTime("120000").good());
188     OFCHECK(ct->getStudy().setStudyID("1").good());
189     OFCHECK(ct->getPatientStudy().setPatientAge("040Y").good());
190     OFCHECK(ct->getSeries().setSeriesDescription("Test Description").good());
191     OFCHECK(ct->getSeries().setSeriesNumber("1").good());
192     OFCHECK(ct->getSeries().setPatientPosition("HFS").good());
193 
194     // Those values are usually computed automatically. UIDS are generated and date/times are set to current values.
195     // But in order to compare the "old" dump with the freshly created image attributes, we set some values manually,
196     // so that they are not overwritten with new, automatically created values later.
197     OFCHECK(ct->getStudy().setStudyInstanceUID("1.2.276.0.7230010.3.1.2.8323329.14863.1565940357.864811").good());
198     OFCHECK(ct->getFrameOfReference().setFrameOfReferenceUID("2.25.30853397773651184949181049330553108086").good());
199     OFCHECK(ct->getSeries().setSeriesInstanceUID("1.2.276.0.7230010.3.1.3.8323329.14863.1565940357.864812").good());
200     OFCHECK(ct->getSOPCommon().setSOPInstanceUID("1.2.276.0.7230010.3.1.4.8323329.14863.1565940357.864813").good());
201 
202     OFCHECK(ct->getIODMultiFrameFGModule().setContentTime("092557").good());
203     OFCHECK(ct->getIODMultiFrameFGModule().setContentDate("20190816").good());
204 }
205 
addSharedFGs(EctEnhancedCT * ct)206 static void addSharedFGs(EctEnhancedCT* ct)
207 {
208     if (!ct)
209         return;
210 
211     FGPixelMeasures meas;
212     OFCHECK(meas.setPixelSpacing("0.1\\0.1").good());
213     OFCHECK(meas.setSliceThickness("1.0").good());
214     OFCHECK(meas.setSpacingBetweenSlices("0.05").good());
215 
216     FGPlanePosPatient planpo;
217     OFCHECK(planpo.setImagePositionPatient("0.0", "0.0", "0.0").good());
218 
219     FGPlaneOrientationPatient planor;
220     OFCHECK(planor.setImageOrientationPatient("1.0", "0.0", "0.0", "0.0", "1.0", "0.0").good());
221 
222     FGFrameAnatomy ana;
223     OFCHECK(ana.setLaterality(FGFrameAnatomy::LATERALITY_BOTH).good());
224     OFCHECK(ana.getAnatomy().getAnatomicRegion().set("12738006", "SCT", "Brain").good());
225 
226     FGFrameVOILUT voi;
227     OFCHECK(voi.setCenterWidthExplanation(1000, 2000, FGFrameVOILUT::DT_CT_WindowCenterWidthExplanation_Brain).good());
228 
229     FGIrradiationEventIdentification irr;
230     OFCHECK(irr.setIrradiationEventUID("2.25.30853892236613436472911970638347155062").good());
231 
232     FGCTImageFrameType itype;
233     OFCHECK(itype.setFrameType("ORIGINAL\\PRIMARY\\VOLUME\\MAXIMUM").good());
234     OFCHECK(itype.setPixelPresentation(FGCTImageFrameType::E_PixelPres_Monochrome).good());
235     OFCHECK(itype.setVolumetricProperties(FGCTImageFrameType::E_VolProp_Volume).good());
236     OFCHECK(itype.setVolumeBasedCalculationTechnique(FGCTImageFrameType::DT_VolBasedCalcTechnique_VolumeRender).good());
237 
238     FGCTAcquisitionType atype;
239     OFCHECK(atype.setAcquisitionType(FGCTAcquisitionType::DT_AcquisitionType_ConstantAngle).good());
240     OFCHECK(atype.setTubeAngle(0.1).good());
241     OFCHECK(atype.setConstantVolumeFlag(FGCTAcquisitionType::E_ConstVol_Yes).good());
242     OFCHECK(atype.setFluoroscopyFlag(FGCTAcquisitionType::E_Fluoroscopy_No).good());
243 
244     FGCTAcquisitionDetails adetails;
245     FGCTAcquisitionDetails::FGCTAcquisitionDetailsItem* item = new FGCTAcquisitionDetails::FGCTAcquisitionDetailsItem();
246     OFCHECK(item->setRotationDirection(FGCTAcquisitionDetails::E_RotationDirection_CW).good());
247     OFCHECK(item->setRevolutionTime(5).good());
248     OFCHECK(item->setSingleCollimationWidth(1).good());
249     OFCHECK(item->setTotalCollimationWidth(10).good());
250     OFCHECK(item->setTableHeight(50).good());
251     OFCHECK(item->setGantryDetectorTilt(5).good());
252     OFCHECK(item->setDataCollectionDiameter(20).good());
253     adetails.getCTAcquisitionDetailsItems().push_back(item);
254 
255     FGCTTableDynamics dyn;
256     FGCTTableDynamics::FGCTTableDynamicsItem* dyn_item = new FGCTTableDynamics::FGCTTableDynamicsItem;
257     OFCHECK(dyn_item);
258     if (dyn_item)
259     {
260         OFCHECK(dyn_item->setTableSpeed(1.0).good());
261         OFCHECK(dyn_item->setTableFeedPerRotation(0.1).good());
262         OFCHECK(dyn_item->setSpiralPitchFactor(0.2).good());
263         dyn.getCTTableDynamicsItems().push_back(dyn_item);
264     }
265 
266     FGCTPosition pos;
267     OFCHECK(pos.setTablePosition(100.0).good());
268     OFCHECK(pos.setReconstructionTargetCenterPatient(OFVector<Float64>(3, 1.0)).good());
269     OFCHECK(pos.setDataCollectionCenterPatient(OFVector<Float64>(3, 2.0)).good());
270 
271     FGCTGeometry geo;
272     FGCTGeometry::FGCTGeometryItem* geo_item = new FGCTGeometry::FGCTGeometryItem;
273     if (geo_item)
274     {
275         OFCHECK(geo_item->setDistanceSourceToDataCollectionCenter(5.0).good());
276         OFCHECK(geo_item->setDistanceSourceToDetector(0.5).good());
277         geo.getCTGeometryItems().push_back(geo_item);
278     }
279 
280     FGCTReconstruction rec;
281     OFCHECK(rec.setConvolutionKernel("DUMMY").good());
282     OFCHECK(rec.setConvolutionKernelGroup("DUMMYGROUP").good());
283     OFCHECK(rec.setImageFilter("FILTER").good());
284     OFCHECK(rec.setReconstructionAlgorithm("ALGO").good());
285     OFCHECK(rec.setReconstructionAngle(90.0).good());
286     OFCHECK(rec.setReconstructionDiameter(100.0).good());
287     // Not permitted if Reconstruction Diameter is provided instead
288     // OFCHECK(rec.setReconstructionFieldOfView(100.0, 100.0).good());
289     OFCHECK(rec.setReconstructionPixelSpacing(0.1, 0.1).good());
290 
291     FGCTExposure exp;
292     FGCTExposure::FGCTExposureItem* exp_item = new FGCTExposure::FGCTExposureItem;
293     if (exp_item)
294     {
295         OFCHECK(exp_item->setCTDIVol(0.1).good());
296         CodeSequenceMacro* phantom_item = new CodeSequenceMacro("113682", "DCM", "ACR Accreditation Phantom - CT");
297         exp_item->getCTDIPhantomTypeCodeSequence().push_back(phantom_item);
298         OFCHECK(exp_item->setEstimatedDoseSaving(0.2).good());
299         OFCHECK(exp_item->setExposureInMas(0.3).good());
300         OFCHECK(exp_item->setExposureModulationType("WEIRD").good());
301         OFCHECK(exp_item->setExposureTimeInMs(0.4).good());
302         OFCHECK(exp_item->setImageAndFluoroscopyAreaDoseProduct(0.5).good());
303         OFCHECK(exp_item->setWaterEquivalentDiameter(0.6).good());
304         CodeSequenceMacro* water_code = new CodeSequenceMacro("113987", "DCM", "AAPM 220");
305         exp_item->getWaterEquivalentDiameterCalculationMethodCodeSequence().push_back(water_code);
306         OFCHECK(exp_item->setXRayTubeCurrentInMa(0.7).good());
307         exp.getCTExposureItems().push_back(exp_item);
308     }
309 
310     FGCTXRayDetails det;
311     FGCTXRayDetails::FGCTXRayDetailsItem* det_item = new FGCTXRayDetails::FGCTXRayDetailsItem;
312     if (det_item)
313     {
314         OFCHECK(det_item->setCalciumScoringMassFactorDevice(OFVector<Float32>(3, 1)).good());
315         OFCHECK(det_item->setCalciumScoringMassFactorPatient(2).good());
316         OFCHECK(det_item->setEnergyWeightingFactor(3).good());
317         OFCHECK(det_item->setFilterMaterial("FILTER_MATERIAL").good());
318         OFCHECK(det_item->setFilterType("FILTER_TYPE").good());
319         OFCHECK(det_item->setFocalSpots(OFVector<Float64>(4, 4.4)).good());
320         OFCHECK(det_item->setKVP(5.0).good());
321         det.getCTXRayDetailsItems().push_back(det_item);
322     }
323 
324     FGPixelValueTransformation trans;
325     trans.setFGType(FGPixelValueTransformation::E_PixelValTrans_CT);
326     trans.setRescaleIntercept("0");
327     trans.setRescaleSlope("1");
328     trans.setRescaleType("HU");
329 
330     FGCTAdditionalXRaySource asrc;
331     FGCTAdditionalXRaySource::FGCTAdditionalXRaySourceItem* asrc_item
332         = new FGCTAdditionalXRaySource::FGCTAdditionalXRaySourceItem;
333     if (asrc_item)
334     {
335         OFCHECK(asrc_item->setDataCollectionDiameter(1.0).good());
336         OFCHECK(asrc_item->setEnergyWeightingFactor(2.0).good());
337         OFCHECK(asrc_item->setExposureInmAs(3.0).good());
338         OFCHECK(asrc_item->setFilterMaterial("FILTER_MATERIAL").good());
339         OFCHECK(asrc_item->setFilterType("FILTER_TYPE").good());
340         OFCHECK(asrc_item->setFocalSpots(OFVector<Float64>(4, 4.4)).good());
341         OFCHECK(asrc_item->setKVP(5).good());
342         OFCHECK(asrc_item->setXRayTubeCurrentInmA(6).good());
343         asrc.getCTAdditionalXRaySourceItems().push_back(asrc_item);
344     }
345 
346     FGTemporalPosition tempos;
347     OFCHECK(tempos.setTemporalPositionTimeOffset(1.0).good());
348 
349     OFCHECK(ct->addForAllFrames(meas).good());
350     OFCHECK(ct->addForAllFrames(planpo).good());
351     OFCHECK(ct->addForAllFrames(planor).good());
352     OFCHECK(ct->addForAllFrames(ana).good());
353     OFCHECK(ct->addForAllFrames(voi).good());
354     OFCHECK(ct->addForAllFrames(irr).good());
355     OFCHECK(ct->addForAllFrames(itype).good());
356     OFCHECK(ct->addForAllFrames(atype).good());
357     OFCHECK(ct->addForAllFrames(adetails).good());
358     OFCHECK(ct->addForAllFrames(dyn).good());
359     OFCHECK(ct->addForAllFrames(pos).good());
360     OFCHECK(ct->addForAllFrames(geo).good());
361     OFCHECK(ct->addForAllFrames(rec).good());
362     OFCHECK(ct->addForAllFrames(exp).good());
363     OFCHECK(ct->addForAllFrames(det).good());
364     OFCHECK(ct->addForAllFrames(trans).good());
365     OFCHECK(ct->addForAllFrames(asrc).good());
366     OFCHECK(ct->addForAllFrames(tempos).good());
367 }
368 
addFrames(EctEnhancedCT * ct)369 static void addFrames(EctEnhancedCT* ct)
370 {
371     if (!ct)
372         return;
373 
374     FGFrameContent* fg = new FGFrameContent();
375     fg->setStackID("1");
376     OFCHECK(fg);
377     if (fg)
378     {
379         EctEnhancedCT::FramesType frames = ct->getFrames();
380         for (Uint16 frameNo = 1; frameNo <= NUM_FRAMES; frameNo++)
381         {
382             OFCHECK(fg->setFrameAcquisitionNumber(frameNo).good());
383             OFCHECK(fg->setFrameReferenceDateTime("20190816092557").good());
384             OFCHECK(fg->setFrameAcquisitionDateTime("20190816092557").good());
385             OFCHECK(fg->setFrameAcquisitionDuration(0.001).good());
386             OFCHECK(fg->setInStackPositionNumber(frameNo).good());
387             OFCHECK(fg->setDimensionIndexValues(1, 0).good());
388             OFCHECK(fg->setDimensionIndexValues(frameNo, 1).good());
389             OFVector<FGBase*> groups;
390             groups.push_back(fg);
391 
392             Uint16* data = new Uint16[NUM_PIXELS_PER_FRAME];
393             for (size_t i = 0; i < NUM_PIXELS_PER_FRAME; ++i)
394             {
395                 data[i] = frameNo;
396             }
397             OFCHECK(
398                 OFget<EctEnhancedCT::Frames<Uint16> >(&frames)->addFrame(data, NUM_PIXELS_PER_FRAME, groups).good());
399             delete[] data;
400         }
401     }
402     delete fg;
403 }
404 
addDimensions(EctEnhancedCT * ct)405 static void addDimensions(EctEnhancedCT* ct)
406 {
407     if (!ct)
408         return;
409     IODMultiframeDimensionModule& dims = ct->getDimensions();
410     OFCHECK(dims.addDimensionIndex(
411                     DCM_StackID, "2.25.30855560781715986879861690673941231222", DCM_FrameContentSequence, "STACK_DIM")
412                 .good());
413     OFCHECK(dims.addDimensionIndex(DCM_InStackPositionNumber,
414                                    "2.25.30855560781715986879861690673941231222",
415                                    DCM_FrameContentSequence,
416                                    "STACK_DIM")
417                 .good());
418     OFunique_ptr<IODMultiframeDimensionModule::DimensionOrganizationItem> org(
419         new IODMultiframeDimensionModule::DimensionOrganizationItem);
420     if (org)
421     {
422         org->setDimensionOrganizationUID("2.25.30855560781715986879861690673941231222");
423         dims.getDimensionOrganizationSequence().push_back(org.release());
424     }
425 }
426 
write(EctEnhancedCT * ct,DcmDataset & ds)427 static OFString write(EctEnhancedCT* ct, DcmDataset& ds)
428 {
429     OFCondition result = ct->writeDataset(ds);
430     OFCHECK(result.good());
431     if (result.bad())
432     {
433         OFLOG_ERROR(tRoundLogger, "Writing Enhanced CT dataset failed: " << result.text() << OFendl);
434     }
435     // Make dump and return it
436     OFStringStream sstream;
437     ds.print(sstream);
438     OFSTRINGSTREAM_GETOFSTRING(sstream, dump);
439     return dump;
440 }
441 
writeAndCheckConcatenation(EctEnhancedCT * ct,OFList<OFFilename> & concats)442 static void writeAndCheckConcatenation(EctEnhancedCT* ct, OFList<OFFilename>& concats)
443 {
444     ConcatenationCreator cc;
445     cc.setCfgFramesPerInstance(NUM_FRAMES_CONCAT);
446     OFCHECK(ct->writeConcatenation(cc).good());
447     size_t numInstances = cc.getNumInstances();
448     OFCHECK(numInstances == NUM_FRAMES);
449     OFCondition result;
450     for (size_t n = 0; n < numInstances; n++)
451     {
452         OFStringStream s;
453         s << "concat_" << n << "_";
454         OFTempFile tf(O_RDWR, "", s.str().c_str(), ".dcm");
455         result = cc.writeNextInstance(tf.getFilename());
456         OFCHECK(result.good());
457         if (result.good())
458         {
459             DcmFileFormat concat;
460             OFCHECK(concat.loadFile(tf.getFilename()).good());
461             checkConcatenationInstance(n, ct, concat.getDataset());
462             concats.push_back(tf.getFilename());
463             tf.stealFile();
464         }
465     }
466 }
467 
checkConcatenationInstance(size_t numInstance,EctEnhancedCT * srcInstance,DcmDataset * concatInstance)468 static void checkConcatenationInstance(size_t numInstance, EctEnhancedCT* srcInstance, DcmDataset* concatInstance)
469 {
470     EctEnhancedCT* concat = NULL;
471     OFCHECK(EctEnhancedCT::loadDataset(*concatInstance, concat).good());
472     if (concat)
473     {
474         size_t numFrames;
475         numFrames = concat->getNumberOfFrames();
476         OFCHECK(numFrames == 1);
477         IODMultiFrameFGModule::ConcatenationInfo& ci = concat->getConcatenationInfo();
478         OFString val;
479         OFCHECK(ci.getConcatenationUID(val).good());
480         OFCHECK(DcmUniqueIdentifier::checkStringValue(val, "1").good());
481         Uint32 frameOffsetNo = 0;
482         OFCHECK(ci.getConcatenationFrameOffsetNumber(frameOffsetNo).good());
483         OFCHECK(frameOffsetNo == numInstance);
484         Uint16 inConcatNo = 0;
485         OFCHECK(ci.getInConcatenationNumber(inConcatNo).good());
486         OFCHECK(inConcatNo == numInstance + 1);
487         Uint16 concatTotalNo = 0;
488         OFCHECK(ci.getInConcatenationTotalNumber(concatTotalNo).good());
489         OFCHECK(concatTotalNo == NUM_FRAMES);
490 
491         OFString srcUID;
492         OFCHECK(ci.getSOPInstanceUIDOfConcatenationSource(srcUID).good());
493         OFCHECK(srcInstance->getSOPCommon().getSOPInstanceUID(val).good());
494         OFCHECK(srcUID == val);
495 
496         OFCHECK(concat->getSOPCommon().getSOPInstanceUID(val).good());
497         OFCHECK(srcUID != val);
498 
499         FunctionalGroups::const_iterator srcShared = srcInstance->getFunctionalGroups().getShared()->begin();
500         FunctionalGroups::const_iterator cShared   = concat->getFunctionalGroups().getShared()->begin();
501         size_t numShared                           = 0;
502         do
503         {
504             OFCHECK(srcShared->second->compare(*cShared->second) == 0);
505             srcShared++;
506             cShared++;
507             numShared++;
508         } while ((srcShared != srcInstance->getFunctionalGroups().getShared()->end())
509                  && (cShared != concat->getFunctionalGroups().getShared()->end()));
510         OFCHECK((srcShared == srcInstance->getFunctionalGroups().getShared()->end())
511                 && (cShared == concat->getFunctionalGroups().getShared()->end()));
512         DcmSequenceOfItems* cPerFrame = NULL;
513         OFCHECK(concatInstance->findAndGetSequence(DCM_PerFrameFunctionalGroupsSequence, cPerFrame).good());
514 
515         if (cPerFrame)
516         {
517             OFCHECK(cPerFrame->card() == 1);
518         }
519 
520         OFBool perFrame = OFFalse;
521         FGBase* fg      = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_CTACQUISITIONDETAILS, perFrame);
522         OFCHECK(fg != NULL);
523         OFCHECK(perFrame == OFFalse);
524 
525         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_CTACQUISITIONTYPE, perFrame);
526         OFCHECK(fg != NULL);
527         OFCHECK(perFrame == OFFalse);
528 
529         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_CTADDITIONALXRAYSOURCE, perFrame);
530         OFCHECK(fg != NULL);
531         OFCHECK(perFrame == OFFalse);
532 
533         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_CTEXPOSURE, perFrame);
534         OFCHECK(fg != NULL);
535         OFCHECK(perFrame == OFFalse);
536 
537         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_CTGEOMETRY, perFrame);
538         OFCHECK(fg != NULL);
539         OFCHECK(perFrame == OFFalse);
540 
541         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_CTIMAGEFRAMETYPE, perFrame);
542         OFCHECK(fg != NULL);
543         OFCHECK(perFrame == OFFalse);
544 
545         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_CTPOSITION, perFrame);
546         OFCHECK(fg != NULL);
547         OFCHECK(perFrame == OFFalse);
548 
549         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_CTRECONSTRUCTION, perFrame);
550         OFCHECK(fg != NULL);
551         OFCHECK(perFrame == OFFalse);
552 
553         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_CTTABLEDYNAMICS, perFrame);
554         OFCHECK(fg != NULL);
555         OFCHECK(perFrame == OFFalse);
556 
557         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_CTXRAYDETAILS, perFrame);
558         OFCHECK(fg != NULL);
559         OFCHECK(perFrame == OFFalse);
560 
561         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_CTXRAYDETAILS, perFrame);
562         OFCHECK(fg != NULL);
563         OFCHECK(perFrame == OFFalse);
564 
565         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_FRAMECONTENT, perFrame);
566         OFCHECK(fg != NULL);
567         OFCHECK(perFrame == OFTrue);
568 
569         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_FRAMEANATOMY, perFrame);
570         OFCHECK(fg != NULL);
571         OFCHECK(perFrame == OFFalse);
572 
573         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_FRAMEANATOMY, perFrame);
574         OFCHECK(fg != NULL);
575         OFCHECK(perFrame == OFFalse);
576 
577         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_FRAMEVOILUTMETA, perFrame);
578         OFCHECK(fg != NULL);
579         OFCHECK(perFrame == OFFalse);
580 
581         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_FRAMEANATOMY, perFrame);
582         OFCHECK(fg != NULL);
583         OFCHECK(perFrame == OFFalse);
584 
585         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_IRRADIATIONEVENTIDENT, perFrame);
586         OFCHECK(fg != NULL);
587         OFCHECK(perFrame == OFFalse);
588 
589         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_PIXELVALUETRANSMETA, perFrame);
590         OFCHECK(fg != NULL);
591         OFCHECK(perFrame == OFFalse);
592 
593         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_IRRADIATIONEVENTIDENT, perFrame);
594         OFCHECK(fg != NULL);
595         OFCHECK(perFrame == OFFalse);
596 
597         fg = NULL;
598         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_PIXELMEASURES, perFrame);
599         OFCHECK(fg != NULL);
600         OFCHECK(perFrame == OFFalse);
601 
602         fg = NULL;
603         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_PLANEPOSPATIENT, perFrame);
604         OFCHECK(fg != NULL);
605         OFCHECK(perFrame == OFFalse);
606 
607         fg = NULL;
608         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_PLANEORIENTPATIENT, perFrame);
609         OFCHECK(fg != NULL);
610         OFCHECK(perFrame == OFFalse);
611 
612         fg = concat->getFunctionalGroups().get(0, DcmFGTypes::EFG_TEMPORALPOSITION, perFrame);
613         OFCHECK(fg != NULL);
614         OFCHECK(perFrame == OFFalse);
615 
616         EctEnhancedCT::FramesType frames = concat->getFrames();
617         Uint16* frame                    = OFget<EctEnhancedCT::Frames<Uint16> >(&frames)->getFrame(0);
618         OFCHECK(frame != OFnullptr);
619         // Check that all pixels are set to their original source instances frame number (starting from 1)
620         for (size_t pix = 0; pix < NUM_PIXELS_PER_FRAME; pix++)
621         {
622             OFCHECK(frame[pix] == numInstance + 1);
623         }
624         delete concat;
625     }
626 }
627 
loadAndCheckConcatenation(const OFList<OFFilename> & concats)628 void loadAndCheckConcatenation(const OFList<OFFilename>& concats)
629 {
630     ConcatenationLoader cl;
631     OFCondition result = cl.scan(concats);
632     OFCHECK(result.good());
633     if (result.good())
634     {
635         DcmDataset merged;
636         EctEnhancedCT* mergedCT = NULL;
637         result                  = EctEnhancedCT::loadConcatenation(cl, cl.getInfo().begin()->first, mergedCT);
638         if (result.good())
639         {
640             OFStringStream s;
641             DcmDataset d;
642             result = mergedCT->writeDataset(d);
643             OFCHECK(result.good());
644             if (result.good())
645             {
646                 // patch in old timestamp to match dump
647                 OFCHECK(d.putAndInsertOFStringArray(DCM_ContentDate, "20190816").good());
648                 OFCHECK(d.putAndInsertOFStringArray(DCM_ContentTime, "092557").good());
649                 d.print(s);
650                 checkCreatedObject(s.str().c_str());
651                 delete mergedCT;
652             }
653         }
654     }
655 }
656 
prepareExpectedDump()657 static void prepareExpectedDump()
658 {
659     EXPECTED_DUMP += "\n";
660     EXPECTED_DUMP += "# Dicom-Data-Set\n";
661     EXPECTED_DUMP += "# Used TransferSyntax: Little Endian Explicit\n";
662     EXPECTED_DUMP += "(0008,0008) CS [ORIGINAL\\PRIMARY\\VOLUME\\MAXIMUM]        #  32, 4 ImageType\n";
663     EXPECTED_DUMP += "(0008,0016) UI =EnhancedCTImageStorage                  #  28, 1 SOPClassUID\n";
664     EXPECTED_DUMP
665         += "(0008,0018) UI [1.2.276.0.7230010.3.1.4.8323329.14863.1565940357.864813] #  56, 1 SOPInstanceUID\n";
666     EXPECTED_DUMP += "(0008,0020) DA [20190801]                               #   8, 1 StudyDate\n";
667     EXPECTED_DUMP += "(0008,0023) DA [20190816]                               #   8, 1 ContentDate\n";
668     EXPECTED_DUMP += "(0008,002a) DT [20190801120000]                         #  14, 1 AcquisitionDateTime\n";
669     EXPECTED_DUMP += "(0008,0030) TM [120000]                                 #   6, 1 StudyTime\n";
670     EXPECTED_DUMP += "(0008,0033) TM [092557]                                 #   6, 1 ContentTime\n";
671     EXPECTED_DUMP += "(0008,0050) SH (no value available)                     #   0, 0 AccessionNumber\n";
672     EXPECTED_DUMP += "(0008,0060) CS [CT]                                     #   2, 1 Modality\n";
673     EXPECTED_DUMP += "(0008,0070) LO [Open Connections]                       #  16, 1 Manufacturer\n";
674     EXPECTED_DUMP += "(0008,0090) PN (no value available)                     #   0, 0 ReferringPhysicianName\n";
675     EXPECTED_DUMP += "(0008,103e) LO [Test Description]                       #  16, 1 SeriesDescription\n";
676     EXPECTED_DUMP += "(0008,1090) LO [OC CT]                                  #   6, 1 ManufacturerModelName\n";
677     EXPECTED_DUMP += "(0008,9205) CS [MONOCHROME]                             #  10, 1 PixelPresentation\n";
678     EXPECTED_DUMP += "(0008,9206) CS [VOLUME]                                 #   6, 1 VolumetricProperties\n";
679     EXPECTED_DUMP
680         += "(0008,9207) CS [VOLUME_RENDER]                          #  14, 1 VolumeBasedCalculationTechnique\n";
681     EXPECTED_DUMP += "(0010,0010) PN [Bond^James]                             #  10, 1 PatientName\n";
682     EXPECTED_DUMP += "(0010,0020) LO [007]                                    #   4, 1 PatientID\n";
683     EXPECTED_DUMP += "(0010,0030) DA [19771007]                               #   8, 1 PatientBirthDate\n";
684     EXPECTED_DUMP += "(0010,0040) CS (no value available)                     #   0, 0 PatientSex\n";
685     EXPECTED_DUMP += "(0010,1010) AS [040Y]                                   #   4, 1 PatientAge\n";
686     EXPECTED_DUMP += "(0018,1000) LO [4711]                                   #   4, 1 DeviceSerialNumber\n";
687     EXPECTED_DUMP += "(0018,1020) LO [0.1]                                    #   4, 1 SoftwareVersions\n";
688     EXPECTED_DUMP += "(0018,5100) CS [HFS]                                    #   4, 1 PatientPosition\n";
689     EXPECTED_DUMP += "(0018,9004) CS [RESEARCH]                               #   8, 1 ContentQualification\n";
690     EXPECTED_DUMP += "(0018,9073) FD 2                                        #   8, 1 AcquisitionDuration\n";
691     EXPECTED_DUMP
692         += "(0020,000d) UI [1.2.276.0.7230010.3.1.2.8323329.14863.1565940357.864811] #  56, 1 StudyInstanceUID\n";
693     EXPECTED_DUMP
694         += "(0020,000e) UI [1.2.276.0.7230010.3.1.3.8323329.14863.1565940357.864812] #  56, 1 SeriesInstanceUID\n";
695     EXPECTED_DUMP += "(0020,0010) SH [1]                                      #   2, 1 StudyID\n";
696     EXPECTED_DUMP += "(0020,0011) IS [1]                                      #   2, 1 SeriesNumber\n";
697     EXPECTED_DUMP += "(0020,0013) IS [1]                                      #   2, 1 InstanceNumber\n";
698     EXPECTED_DUMP += "(0020,0052) UI [2.25.30853397773651184949181049330553108086] #  44, 1 FrameOfReferenceUID\n";
699     EXPECTED_DUMP += "(0020,1040) LO (no value available)                     #   0, 0 PositionReferenceIndicator\n";
700     EXPECTED_DUMP += "(0020,9221) SQ (Sequence with explicit length #=1)      #   0, 1 DimensionOrganizationSequence\n";
701     EXPECTED_DUMP += "  (fffe,e000) na (Item with explicit length #=1)          #   0, 1 Item\n";
702     EXPECTED_DUMP
703         += "    (0020,9164) UI [2.25.30855560781715986879861690673941231222] #  44, 1 DimensionOrganizationUID\n";
704     EXPECTED_DUMP += "  (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
705     EXPECTED_DUMP += "(fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
706     EXPECTED_DUMP += "(0020,9222) SQ (Sequence with explicit length #=2)      #   0, 1 DimensionIndexSequence\n";
707     EXPECTED_DUMP += "  (fffe,e000) na (Item with explicit length #=4)          #   0, 1 Item\n";
708     EXPECTED_DUMP
709         += "    (0020,9164) UI [2.25.30855560781715986879861690673941231222] #  44, 1 DimensionOrganizationUID\n";
710     EXPECTED_DUMP += "    (0020,9165) AT (0020,9056)                              #   4, 1 DimensionIndexPointer\n";
711     EXPECTED_DUMP += "    (0020,9167) AT (0020,9111)                              #   4, 1 FunctionalGroupPointer\n";
712     EXPECTED_DUMP += "    (0020,9421) LO [STACK_DIM]                              #  10, 1 DimensionDescriptionLabel\n";
713     EXPECTED_DUMP += "  (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
714     EXPECTED_DUMP += "  (fffe,e000) na (Item with explicit length #=4)          #   0, 1 Item\n";
715     EXPECTED_DUMP
716         += "    (0020,9164) UI [2.25.30855560781715986879861690673941231222] #  44, 1 DimensionOrganizationUID\n";
717     EXPECTED_DUMP += "    (0020,9165) AT (0020,9057)                              #   4, 1 DimensionIndexPointer\n";
718     EXPECTED_DUMP += "    (0020,9167) AT (0020,9111)                              #   4, 1 FunctionalGroupPointer\n";
719     EXPECTED_DUMP += "    (0020,9421) LO [STACK_DIM]                              #  10, 1 DimensionDescriptionLabel\n";
720     EXPECTED_DUMP += "  (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
721     EXPECTED_DUMP += "(fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
722     EXPECTED_DUMP += "(0028,0002) US 1                                        #   2, 1 SamplesPerPixel\n";
723     EXPECTED_DUMP += "(0028,0004) CS [MONOCHROME2]                            #  12, 1 PhotometricInterpretation\n";
724     EXPECTED_DUMP += "(0028,0008) IS [2]                                      #   2, 1 NumberOfFrames\n";
725     EXPECTED_DUMP += "(0028,0010) US 2                                        #   2, 1 Rows\n";
726     EXPECTED_DUMP += "(0028,0011) US 2                                        #   2, 1 Columns\n";
727     EXPECTED_DUMP += "(0028,0100) US 16                                       #   2, 1 BitsAllocated\n";
728     EXPECTED_DUMP += "(0028,0101) US 16                                       #   2, 1 BitsStored\n";
729     EXPECTED_DUMP += "(0028,0102) US 15                                       #   2, 1 HighBit\n";
730     EXPECTED_DUMP += "(0028,0103) US 0                                        #   2, 1 PixelRepresentation\n";
731     EXPECTED_DUMP += "(0028,0301) CS [NO]                                     #   2, 1 BurnedInAnnotation\n";
732     EXPECTED_DUMP += "(0028,2110) CS [00]                                     #   2, 1 LossyImageCompression\n";
733     EXPECTED_DUMP += "(0040,0555) SQ (Sequence with explicit length #=0)      #   0, 1 AcquisitionContextSequence\n";
734     EXPECTED_DUMP += "(fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
735     EXPECTED_DUMP += "(2050,0020) CS [IDENTITY]                               #   8, 1 PresentationLUTShape\n";
736     EXPECTED_DUMP
737         += "(5200,9229) SQ (Sequence with explicit length #=1)      #   0, 1 SharedFunctionalGroupsSequence\n";
738     EXPECTED_DUMP += "  (fffe,e000) na (Item with explicit length #=18)         #   0, 1 Item\n";
739     EXPECTED_DUMP += "    (0018,9301) SQ (Sequence with explicit length #=1)      #   0, 1 CTAcquisitionTypeSequence\n";
740     EXPECTED_DUMP += "      (fffe,e000) na (Item with explicit length #=4)          #   0, 1 Item\n";
741     EXPECTED_DUMP += "        (0018,9302) CS [CONSTANT_ANGLE]                         #  14, 1 AcquisitionType\n";
742     EXPECTED_DUMP += "        (0018,9303) FD 0.1                                      #   8, 1 TubeAngle\n";
743     EXPECTED_DUMP += "        (0018,9333) CS [YES]                                    #   4, 1 ConstantVolumeFlag\n";
744     EXPECTED_DUMP += "        (0018,9334) CS [NO]                                     #   2, 1 FluoroscopyFlag\n";
745     EXPECTED_DUMP += "      (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
746     EXPECTED_DUMP += "    (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
747     EXPECTED_DUMP
748         += "    (0018,9304) SQ (Sequence with explicit length #=1)      #   0, 1 CTAcquisitionDetailsSequence\n";
749     EXPECTED_DUMP += "      (fffe,e000) na (Item with explicit length #=7)          #   0, 1 Item\n";
750     EXPECTED_DUMP
751         += "        (0018,0090) DS [20]                                     #   2, 1 DataCollectionDiameter\n";
752     EXPECTED_DUMP += "        (0018,1120) DS [5]                                      #   2, 1 GantryDetectorTilt\n";
753     EXPECTED_DUMP += "        (0018,1130) DS [50]                                     #   2, 1 TableHeight\n";
754     EXPECTED_DUMP += "        (0018,1140) CS [CW]                                     #   2, 1 RotationDirection\n";
755     EXPECTED_DUMP += "        (0018,9305) FD 5                                        #   8, 1 RevolutionTime\n";
756     EXPECTED_DUMP
757         += "        (0018,9306) FD 1                                        #   8, 1 SingleCollimationWidth\n";
758     EXPECTED_DUMP += "        (0018,9307) FD 10                                       #   8, 1 TotalCollimationWidth\n";
759     EXPECTED_DUMP += "      (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
760     EXPECTED_DUMP += "    (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
761     EXPECTED_DUMP += "    (0018,9308) SQ (Sequence with explicit length #=1)      #   0, 1 CTTableDynamicsSequence\n";
762     EXPECTED_DUMP += "      (fffe,e000) na (Item with explicit length #=3)          #   0, 1 Item\n";
763     EXPECTED_DUMP += "        (0018,9309) FD 1                                        #   8, 1 TableSpeed\n";
764     EXPECTED_DUMP += "        (0018,9310) FD 0.1                                      #   8, 1 TableFeedPerRotation\n";
765     EXPECTED_DUMP += "        (0018,9311) FD 0.2                                      #   8, 1 SpiralPitchFactor\n";
766     EXPECTED_DUMP += "      (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
767     EXPECTED_DUMP += "    (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
768     EXPECTED_DUMP += "    (0018,9312) SQ (Sequence with explicit length #=1)      #   0, 1 CTGeometrySequence\n";
769     EXPECTED_DUMP += "      (fffe,e000) na (Item with explicit length #=2)          #   0, 1 Item\n";
770     EXPECTED_DUMP
771         += "        (0018,1110) DS [0.5]                                    #   4, 1 DistanceSourceToDetector\n";
772     EXPECTED_DUMP += "        (0018,9335) FD 5                                        #   8, 1 "
773                      "DistanceSourceToDataCollectionCenter\n";
774     EXPECTED_DUMP += "      (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
775     EXPECTED_DUMP += "    (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
776     EXPECTED_DUMP += "    (0018,9314) SQ (Sequence with explicit length #=1)      #   0, 1 CTReconstructionSequence\n";
777     EXPECTED_DUMP += "      (fffe,e000) na (Item with explicit length #=7)          #   0, 1 Item\n";
778     EXPECTED_DUMP
779         += "        (0018,1100) DS [100]                                    #   4, 1 ReconstructionDiameter\n";
780     EXPECTED_DUMP += "        (0018,1210) SH [DUMMY]                                  #   6, 1 ConvolutionKernel\n";
781     EXPECTED_DUMP
782         += "        (0018,9315) CS [ALGO]                                   #   4, 1 ReconstructionAlgorithm\n";
783     EXPECTED_DUMP
784         += "        (0018,9316) CS [DUMMYGROUP]                             #  10, 1 ConvolutionKernelGroup\n";
785     EXPECTED_DUMP += "        (0018,9319) FD 90                                       #   8, 1 ReconstructionAngle\n";
786     EXPECTED_DUMP += "        (0018,9320) SH [FILTER]                                 #   6, 1 ImageFilter\n";
787     EXPECTED_DUMP
788         += "        (0018,9322) FD 0.1\\0.1                                  #  16, 2 ReconstructionPixelSpacing\n";
789     EXPECTED_DUMP += "      (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
790     EXPECTED_DUMP += "    (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
791     EXPECTED_DUMP += "    (0018,9321) SQ (Sequence with explicit length #=1)      #   0, 1 CTExposureSequence\n";
792     EXPECTED_DUMP += "      (fffe,e000) na (Item with explicit length #=10)         #   0, 1 Item\n";
793     EXPECTED_DUMP += "        (0018,115e) DS [0.5]                                    #   4, 1 "
794                      "ImageAndFluoroscopyAreaDoseProduct\n";
795     EXPECTED_DUMP
796         += "        (0018,1271) FD 0.6                                      #   8, 1 WaterEquivalentDiameter\n";
797     EXPECTED_DUMP += "        (0018,1272) SQ (Sequence with explicit length #=1)      #   0, 1 "
798                      "WaterEquivalentDiameterCalculationMethodCodeSequence\n";
799     EXPECTED_DUMP += "          (fffe,e000) na (Item with explicit length #=3)          #   0, 1 Item\n";
800     EXPECTED_DUMP += "            (0008,0100) SH [113987]                                 #   6, 1 CodeValue\n";
801     EXPECTED_DUMP
802         += "            (0008,0102) SH [DCM]                                    #   4, 1 CodingSchemeDesignator\n";
803     EXPECTED_DUMP += "            (0008,0104) LO [AAPM 220]                               #   8, 1 CodeMeaning\n";
804     EXPECTED_DUMP
805         += "          (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
806     EXPECTED_DUMP
807         += "        (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
808     EXPECTED_DUMP
809         += "        (0018,9323) CS [WEIRD]                                  #   6, 1 ExposureModulationType\n";
810     EXPECTED_DUMP += "        (0018,9324) FD 0.2                                      #   8, 1 EstimatedDoseSaving\n";
811     EXPECTED_DUMP += "        (0018,9328) FD 0.4                                      #   8, 1 ExposureTimeInms\n";
812     EXPECTED_DUMP += "        (0018,9330) FD 0.7                                      #   8, 1 XRayTubeCurrentInmA\n";
813     EXPECTED_DUMP += "        (0018,9332) FD 0.3                                      #   8, 1 ExposureInmAs\n";
814     EXPECTED_DUMP += "        (0018,9345) FD 0.1                                      #   8, 1 CTDIvol\n";
815     EXPECTED_DUMP
816         += "        (0018,9346) SQ (Sequence with explicit length #=1)      #   0, 1 CTDIPhantomTypeCodeSequence\n";
817     EXPECTED_DUMP += "          (fffe,e000) na (Item with explicit length #=3)          #   0, 1 Item\n";
818     EXPECTED_DUMP += "            (0008,0100) SH [113682]                                 #   6, 1 CodeValue\n";
819     EXPECTED_DUMP
820         += "            (0008,0102) SH [DCM]                                    #   4, 1 CodingSchemeDesignator\n";
821     EXPECTED_DUMP += "            (0008,0104) LO [ACR Accreditation Phantom - CT]         #  30, 1 CodeMeaning\n";
822     EXPECTED_DUMP
823         += "          (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
824     EXPECTED_DUMP
825         += "        (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
826     EXPECTED_DUMP += "      (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
827     EXPECTED_DUMP += "    (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
828     EXPECTED_DUMP += "    (0018,9325) SQ (Sequence with explicit length #=1)      #   0, 1 CTXRayDetailsSequence\n";
829     EXPECTED_DUMP += "      (fffe,e000) na (Item with explicit length #=7)          #   0, 1 Item\n";
830     EXPECTED_DUMP += "        (0018,0060) DS [5]                                      #   2, 1 KVP\n";
831     EXPECTED_DUMP += "        (0018,1160) SH [FILTER_TYPE]                            #  12, 1 FilterType\n";
832     EXPECTED_DUMP += "        (0018,1190) DS [4.4\\4.4\\4.4\\4.4]                        #  16, 4 FocalSpots\n";
833     EXPECTED_DUMP += "        (0018,7050) CS [FILTER_MATERIAL]                        #  16, 1 FilterMaterial\n";
834     EXPECTED_DUMP
835         += "        (0018,9351) FL 2                                        #   4, 1 CalciumScoringMassFactorPatient\n";
836     EXPECTED_DUMP += "        (0018,9352) FL 1\\1\\1                                    #  12, 3 "
837                      "CalciumScoringMassFactorDevice\n";
838     EXPECTED_DUMP += "        (0018,9353) FL 3                                        #   4, 1 EnergyWeightingFactor\n";
839     EXPECTED_DUMP += "      (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
840     EXPECTED_DUMP += "    (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
841     EXPECTED_DUMP += "    (0018,9326) SQ (Sequence with explicit length #=1)      #   0, 1 CTPositionSequence\n";
842     EXPECTED_DUMP += "      (fffe,e000) na (Item with explicit length #=3)          #   0, 1 Item\n";
843     EXPECTED_DUMP
844         += "        (0018,9313) FD 2\\2\\2                                    #  24, 3 DataCollectionCenterPatient\n";
845     EXPECTED_DUMP += "        (0018,9318) FD 1\\1\\1                                    #  24, 3 "
846                      "ReconstructionTargetCenterPatient\n";
847     EXPECTED_DUMP += "        (0018,9327) FD 100                                      #   8, 1 TablePosition\n";
848     EXPECTED_DUMP += "      (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
849     EXPECTED_DUMP += "    (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
850     EXPECTED_DUMP += "    (0018,9329) SQ (Sequence with explicit length #=1)      #   0, 1 CTImageFrameTypeSequence\n";
851     EXPECTED_DUMP += "      (fffe,e000) na (Item with explicit length #=4)          #   0, 1 Item\n";
852     EXPECTED_DUMP += "        (0008,9007) CS [ORIGINAL\\PRIMARY\\VOLUME\\MAXIMUM]        #  32, 4 FrameType\n";
853     EXPECTED_DUMP += "        (0008,9205) CS [MONOCHROME]                             #  10, 1 PixelPresentation\n";
854     EXPECTED_DUMP += "        (0008,9206) CS [VOLUME]                                 #   6, 1 VolumetricProperties\n";
855     EXPECTED_DUMP
856         += "        (0008,9207) CS [VOLUME_RENDER]                          #  14, 1 VolumeBasedCalculationTechnique\n";
857     EXPECTED_DUMP += "      (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
858     EXPECTED_DUMP += "    (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
859     EXPECTED_DUMP
860         += "    (0018,9360) SQ (Sequence with explicit length #=1)      #   0, 1 CTAdditionalXRaySourceSequence\n";
861     EXPECTED_DUMP += "      (fffe,e000) na (Item with explicit length #=8)          #   0, 1 Item\n";
862     EXPECTED_DUMP += "        (0018,0060) DS [5]                                      #   2, 1 KVP\n";
863     EXPECTED_DUMP
864         += "        (0018,0090) DS [1]                                      #   2, 1 DataCollectionDiameter\n";
865     EXPECTED_DUMP += "        (0018,1160) SH [FILTER_TYPE]                            #  12, 1 FilterType\n";
866     EXPECTED_DUMP += "        (0018,1190) DS [4.4\\4.4\\4.4\\4.4]                        #  16, 4 FocalSpots\n";
867     EXPECTED_DUMP += "        (0018,7050) CS [FILTER_MATERIAL]                        #  16, 1 FilterMaterial\n";
868     EXPECTED_DUMP += "        (0018,9330) FD 6                                        #   8, 1 XRayTubeCurrentInmA\n";
869     EXPECTED_DUMP += "        (0018,9332) FD 3                                        #   8, 1 ExposureInmAs\n";
870     EXPECTED_DUMP += "        (0018,9353) FL 2                                        #   4, 1 EnergyWeightingFactor\n";
871     EXPECTED_DUMP += "      (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
872     EXPECTED_DUMP += "    (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
873     EXPECTED_DUMP += "    (0018,9477) SQ (Sequence with explicit length #=1)      #   0, 1 "
874                      "IrradiationEventIdentificationSequence\n";
875     EXPECTED_DUMP += "      (fffe,e000) na (Item with explicit length #=1)          #   0, 1 Item\n";
876     EXPECTED_DUMP
877         += "        (0008,3010) UI [2.25.30853892236613436472911970638347155062] #  44, 1 IrradiationEventUID\n";
878     EXPECTED_DUMP += "      (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
879     EXPECTED_DUMP += "    (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
880     EXPECTED_DUMP += "    (0020,9071) SQ (Sequence with explicit length #=1)      #   0, 1 FrameAnatomySequence\n";
881     EXPECTED_DUMP += "      (fffe,e000) na (Item with explicit length #=2)          #   0, 1 Item\n";
882     EXPECTED_DUMP
883         += "        (0008,2218) SQ (Sequence with explicit length #=1)      #   0, 1 AnatomicRegionSequence\n";
884     EXPECTED_DUMP += "          (fffe,e000) na (Item with explicit length #=3)          #   0, 1 Item\n";
885     EXPECTED_DUMP += "            (0008,0100) SH [12738006]                               #   8, 1 CodeValue\n";
886     EXPECTED_DUMP
887         += "            (0008,0102) SH [SCT]                                    #   4, 1 CodingSchemeDesignator\n";
888     EXPECTED_DUMP += "            (0008,0104) LO [Brain]                                  #   6, 1 CodeMeaning\n";
889     EXPECTED_DUMP
890         += "          (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
891     EXPECTED_DUMP
892         += "        (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
893     EXPECTED_DUMP += "        (0020,9072) CS [B]                                      #   2, 1 FrameLaterality\n";
894     EXPECTED_DUMP += "      (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
895     EXPECTED_DUMP += "    (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
896     EXPECTED_DUMP += "    (0020,9113) SQ (Sequence with explicit length #=1)      #   0, 1 PlanePositionSequence\n";
897     EXPECTED_DUMP += "      (fffe,e000) na (Item with explicit length #=1)          #   0, 1 Item\n";
898     EXPECTED_DUMP
899         += "        (0020,0032) DS [0.0\\0.0\\0.0]                            #  12, 3 ImagePositionPatient\n";
900     EXPECTED_DUMP += "      (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
901     EXPECTED_DUMP += "    (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
902     EXPECTED_DUMP += "    (0020,9116) SQ (Sequence with explicit length #=1)      #   0, 1 PlaneOrientationSequence\n";
903     EXPECTED_DUMP += "      (fffe,e000) na (Item with explicit length #=1)          #   0, 1 Item\n";
904     EXPECTED_DUMP
905         += "        (0020,0037) DS [1.0\\0.0\\0.0\\0.0\\1.0\\0.0]                #  24, 6 ImageOrientationPatient\n";
906     EXPECTED_DUMP += "      (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
907     EXPECTED_DUMP += "    (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
908     EXPECTED_DUMP += "    (0020,9310) SQ (Sequence with explicit length #=1)      #   0, 1 TemporalPositionSequence\n";
909     EXPECTED_DUMP += "      (fffe,e000) na (Item with explicit length #=1)          #   0, 1 Item\n";
910     EXPECTED_DUMP
911         += "        (0020,930d) FD 1                                        #   8, 1 TemporalPositionTimeOffset\n";
912     EXPECTED_DUMP += "      (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
913     EXPECTED_DUMP += "    (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
914     EXPECTED_DUMP += "    (0028,9110) SQ (Sequence with explicit length #=1)      #   0, 1 PixelMeasuresSequence\n";
915     EXPECTED_DUMP += "      (fffe,e000) na (Item with explicit length #=3)          #   0, 1 Item\n";
916     EXPECTED_DUMP += "        (0018,0050) DS [1.0]                                    #   4, 1 SliceThickness\n";
917     EXPECTED_DUMP += "        (0018,0088) DS [0.05]                                   #   4, 1 SpacingBetweenSlices\n";
918     EXPECTED_DUMP += "        (0028,0030) DS [0.1\\0.1]                                #   8, 2 PixelSpacing\n";
919     EXPECTED_DUMP += "      (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
920     EXPECTED_DUMP += "    (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
921     EXPECTED_DUMP += "    (0028,9132) SQ (Sequence with explicit length #=1)      #   0, 1 FrameVOILUTSequence\n";
922     EXPECTED_DUMP += "      (fffe,e000) na (Item with explicit length #=3)          #   0, 1 Item\n";
923     EXPECTED_DUMP += "        (0028,1050) DS [1000]                                   #   4, 1 WindowCenter\n";
924     EXPECTED_DUMP += "        (0028,1051) DS [2000]                                   #   4, 1 WindowWidth\n";
925     EXPECTED_DUMP
926         += "        (0028,1055) LO [BRAIN]                                  #   6, 1 WindowCenterWidthExplanation\n";
927     EXPECTED_DUMP += "      (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
928     EXPECTED_DUMP += "    (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
929     EXPECTED_DUMP
930         += "    (0028,9145) SQ (Sequence with explicit length #=1)      #   0, 1 PixelValueTransformationSequence\n";
931     EXPECTED_DUMP += "      (fffe,e000) na (Item with explicit length #=3)          #   0, 1 Item\n";
932     EXPECTED_DUMP += "        (0028,1052) DS [0]                                      #   2, 1 RescaleIntercept\n";
933     EXPECTED_DUMP += "        (0028,1053) DS [1]                                      #   2, 1 RescaleSlope\n";
934     EXPECTED_DUMP += "        (0028,1054) LO [HU]                                     #   2, 1 RescaleType\n";
935     EXPECTED_DUMP += "      (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
936     EXPECTED_DUMP += "    (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
937     EXPECTED_DUMP += "  (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
938     EXPECTED_DUMP += "(fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
939     EXPECTED_DUMP
940         += "(5200,9230) SQ (Sequence with explicit length #=2)      #   0, 1 PerFrameFunctionalGroupsSequence\n";
941     EXPECTED_DUMP += "  (fffe,e000) na (Item with explicit length #=1)          #   0, 1 Item\n";
942     EXPECTED_DUMP += "    (0020,9111) SQ (Sequence with explicit length #=1)      #   0, 1 FrameContentSequence\n";
943     EXPECTED_DUMP += "      (fffe,e000) na (Item with explicit length #=7)          #   0, 1 Item\n";
944     EXPECTED_DUMP
945         += "        (0018,9074) DT [20190816092557]                         #  14, 1 FrameAcquisitionDateTime\n";
946     EXPECTED_DUMP
947         += "        (0018,9151) DT [20190816092557]                         #  14, 1 FrameReferenceDateTime\n";
948     EXPECTED_DUMP
949         += "        (0018,9220) FD 0.001                                    #   8, 1 FrameAcquisitionDuration\n";
950     EXPECTED_DUMP += "        (0020,9056) SH [1]                                      #   2, 1 StackID\n";
951     EXPECTED_DUMP += "        (0020,9057) UL 1                                        #   4, 1 InStackPositionNumber\n";
952     EXPECTED_DUMP
953         += "        (0020,9156) US 1                                        #   2, 1 FrameAcquisitionNumber\n";
954     EXPECTED_DUMP += "        (0020,9157) UL 1\\1                                      #   8, 2 DimensionIndexValues\n";
955     EXPECTED_DUMP += "      (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
956     EXPECTED_DUMP += "    (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
957     EXPECTED_DUMP += "  (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
958     EXPECTED_DUMP += "  (fffe,e000) na (Item with explicit length #=1)          #   0, 1 Item\n";
959     EXPECTED_DUMP += "    (0020,9111) SQ (Sequence with explicit length #=1)      #   0, 1 FrameContentSequence\n";
960     EXPECTED_DUMP += "      (fffe,e000) na (Item with explicit length #=7)          #   0, 1 Item\n";
961     EXPECTED_DUMP
962         += "        (0018,9074) DT [20190816092557]                         #  14, 1 FrameAcquisitionDateTime\n";
963     EXPECTED_DUMP
964         += "        (0018,9151) DT [20190816092557]                         #  14, 1 FrameReferenceDateTime\n";
965     EXPECTED_DUMP
966         += "        (0018,9220) FD 0.001                                    #   8, 1 FrameAcquisitionDuration\n";
967     EXPECTED_DUMP += "        (0020,9056) SH [1]                                      #   2, 1 StackID\n";
968     EXPECTED_DUMP += "        (0020,9057) UL 2                                        #   4, 1 InStackPositionNumber\n";
969     EXPECTED_DUMP
970         += "        (0020,9156) US 2                                        #   2, 1 FrameAcquisitionNumber\n";
971     EXPECTED_DUMP += "        (0020,9157) UL 1\\2                                      #   8, 2 DimensionIndexValues\n";
972     EXPECTED_DUMP += "      (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
973     EXPECTED_DUMP += "    (fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
974     EXPECTED_DUMP += "  (fffe,e00d) na (ItemDelimitationItem for re-encoding)   #   0, 0 ItemDelimitationItem\n";
975     EXPECTED_DUMP += "(fffe,e0dd) na (SequenceDelimitationItem for re-encod.) #   0, 0 SequenceDelimitationItem\n";
976     EXPECTED_DUMP += "(7fe0,0010) OW 0001\\0001\\0001\\0001\\0002\\0002\\0002\\0002  #  16, 1 PixelData\n";
977 }
978 
checkCreatedObject(const OFString & ds_dump)979 static void checkCreatedObject(const OFString& ds_dump)
980 {
981     OFBool dump_ok = (ds_dump == EXPECTED_DUMP);
982     OFCHECK(dump_ok);
983     if (!dump_ok)
984     {
985         CERR << "Dump produced: " << OFendl << ds_dump << OFendl;
986         CERR << "------------------------------------" << OFendl;
987         CERR << "Dump expected: " << OFendl << EXPECTED_DUMP << OFendl;
988         CERR << "------------------------------------" << OFendl;
989     }
990 }
991