1 /*
2  *
3  *  Copyright (C) 2016-2019, J. Riesmeier, Oldenburg, Germany
4  *  All rights reserved.  See COPYRIGHT file for details.
5  *
6  *  Source file for class TID1411_VolumetricROIMeasurements
7  *
8  *  Author: Joerg Riesmeier
9  *
10  */
11 
12 
13 #include "dcmtk/config/osconfig.h"    /* make sure OS specific configuration is included first */
14 
15 #include "dcmtk/dcmsr/cmr/tid1411.h"
16 #include "dcmtk/dcmsr/cmr/tid15def.h"
17 #include "dcmtk/dcmsr/cmr/logger.h"
18 #include "dcmtk/dcmsr/codes/dcm.h"
19 #include "dcmtk/dcmsr/codes/ncit.h"
20 #include "dcmtk/dcmsr/codes/sct.h"
21 #include "dcmtk/dcmsr/codes/umls.h"
22 #include "dcmtk/dcmsr/dsrtpltn.h"
23 
24 #include "dcmtk/dcmdata/dcdeftag.h"
25 #include "dcmtk/dcmdata/dcuid.h"
26 
27 
28 // helper macros for checking the return value of API calls
29 #define CHECK_RESULT(call) if (result.good()) result = call
30 #define STORE_RESULT(call) result = call
31 #define GOOD_RESULT(call) if (result.good()) call
32 #define BAD_RESULT(call) if (result.bad()) call
33 
34 // index positions in node list (makes source code more readable)
35 #define MEASUREMENT_GROUP               0
36 #define ACTIVITY_SESSION                1
37 #define TRACKING_IDENTIFIER             2
38 #define TRACKING_UNIQUE_IDENTIFIER      3
39 #define FINDING                         4
40 #define TIME_POINT                      5
41 #define REFERENCED_SEGMENT              6
42 #define SOURCE_SERIES_FOR_SEGMENTATION  7
43 #define REAL_WORLD_VALUE_MAP            8
44 #define MEASUREMENT_METHOD              9
45 #define LAST_FINDING_SITE              10
46 #define LAST_MEASUREMENT               11
47 #define LAST_QUALITATIVE_EVALUATION    12
48 #define NUMBER_OF_LIST_ENTRIES         13
49 
50 // general information on TID 1411 (Volumetric ROI Measurements)
51 #define TEMPLATE_NUMBER      "1411"
52 #define MAPPING_RESOURCE     "DCMR"
53 #define MAPPING_RESOURCE_UID UID_DICOMContentMappingResource
54 #define TEMPLATE_TYPE        OFTrue   /* extensible */
55 #define TEMPLATE_ORDER       OFFalse  /* non-significant */
56 
57 
58 template<typename T1, typename T2, typename T3, typename T4>
TID1411_VolumetricROIMeasurements(const OFBool createGroup)59 TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::TID1411_VolumetricROIMeasurements(const OFBool createGroup)
60   : DSRSubTemplate(TEMPLATE_NUMBER, MAPPING_RESOURCE, MAPPING_RESOURCE_UID),
61     Measurement(new TID1419_Measurement())
62 {
63     setExtensible(TEMPLATE_TYPE);
64     setOrderSignificant(TEMPLATE_ORDER);
65     /* need to store position of various content items */
66     reserveEntriesInNodeList(NUMBER_OF_LIST_ENTRIES, OFTrue /*initialize*/);
67     /* TID 1411 (Volumetric ROI Measurements) Row 1 */
68     if (createGroup)
69         createMeasurementGroup();
70 }
71 
72 
73 template<typename T1, typename T2, typename T3, typename T4>
clear()74 void TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::clear()
75 {
76     DSRSubTemplate::clear();
77     Measurement->clear();
78 }
79 
80 
81 template<typename T1, typename T2, typename T3, typename T4>
isValid() const82 OFBool TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::isValid() const
83 {
84     /* check whether base class is valid and all required content items are present */
85     return DSRSubTemplate::isValid() &&
86         hasMeasurementGroup() && hasTrackingIdentifier() && hasTrackingUniqueIdentifier() &&
87         hasReferencedSegment() && hasSourceSeriesForSegmentation() && hasMeasurements(OFTrue /*checkChildren*/);
88 }
89 
90 
91 template<typename T1, typename T2, typename T3, typename T4>
hasMeasurementGroup(const OFBool checkChildren) const92 OFBool TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::hasMeasurementGroup(const OFBool checkChildren) const
93 {
94     OFBool result = OFFalse;
95     /* need to check for child nodes? */
96     if (checkChildren)
97     {
98         DSRDocumentTreeNodeCursor cursor(getRoot());
99         /* go to content item at TID 1411 (Volumetric ROI Measurements) Row 1 */
100         if (gotoEntryFromNodeList(cursor, MEASUREMENT_GROUP) > 0)
101             result = cursor.hasChildNodes();
102     } else {
103         /* check for content item at TID 1411 (Volumetric ROI Measurements) Row 1 */
104         result = (getEntryFromNodeList(MEASUREMENT_GROUP) > 0);
105     }
106     return result;
107 }
108 
109 
110 template<typename T1, typename T2, typename T3, typename T4>
hasTrackingIdentifier() const111 OFBool TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::hasTrackingIdentifier() const
112 {
113     /* check for content item at TID 1411 (Volumetric ROI Measurements) Row 2 */
114     return (getEntryFromNodeList(TRACKING_IDENTIFIER) > 0);
115 }
116 
117 
118 template<typename T1, typename T2, typename T3, typename T4>
hasTrackingUniqueIdentifier() const119 OFBool TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::hasTrackingUniqueIdentifier() const
120 {
121     /* check for content item at TID 1411 (Volumetric ROI Measurements) Row 3 */
122     return (getEntryFromNodeList(TRACKING_UNIQUE_IDENTIFIER) > 0);
123 }
124 
125 
126 template<typename T1, typename T2, typename T3, typename T4>
hasReferencedSegment() const127 OFBool TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::hasReferencedSegment() const
128 {
129     /* check for content item at TID 1411 (Volumetric ROI Measurements) Row 7 */
130     return (getEntryFromNodeList(REFERENCED_SEGMENT) > 0);
131 }
132 
133 
134 template<typename T1, typename T2, typename T3, typename T4>
hasSourceSeriesForSegmentation() const135 OFBool TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::hasSourceSeriesForSegmentation() const
136 {
137     /* check for content item at TID 1411 (Volumetric ROI Measurements) Row 12 */
138     return (getEntryFromNodeList(SOURCE_SERIES_FOR_SEGMENTATION) > 0);
139 }
140 
141 
142 template<typename T1, typename T2, typename T3, typename T4>
hasMeasurements(const OFBool checkChildren) const143 OFBool TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::hasMeasurements(const OFBool checkChildren) const
144 {
145     OFBool result = OFFalse;
146     /* need to check for child nodes? */
147     if (checkChildren)
148     {
149         DSRDocumentTreeNodeCursor cursor(getRoot());
150         /* go to content item at TID 1411 (Volumetric ROI Measurements) Row 13 */
151         if (gotoEntryFromNodeList(cursor, LAST_MEASUREMENT) > 0)
152         {
153             /* check whether any of the "included TID 1419 templates" is non-empty */
154             while (cursor.isValid() && (cursor.getNode()->getValueType() == VT_includedTemplate))
155             {
156                 const DSRSubTemplate *subTempl = OFstatic_cast(const DSRIncludedTemplateTreeNode *, cursor.getNode())->getValue().get();
157                 if (subTempl != NULL)
158                 {
159                     if (subTempl->compareTemplateIdentication("1419", "DCMR"))
160                     {
161                         result = !subTempl->isEmpty();
162                         if (result) break;
163                     } else {
164                         /* exit loop */
165                         break;
166                     }
167                 }
168                 if (cursor.gotoPrevious() == 0)
169                 {
170                     /* invalidate cursor */
171                     cursor.clear();
172                 }
173             }
174         }
175     } else {
176         /* check for content item at TID 1411 (Volumetric ROI Measurements) Row 13 */
177         result = (getEntryFromNodeList(LAST_MEASUREMENT) > 0);
178     }
179     return result;
180 }
181 
182 
183 template<typename T1, typename T2, typename T3, typename T4>
setActivitySession(const OFString & session,const OFBool check)184 OFCondition TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::setActivitySession(const OFString &session,
185                                                                                   const OFBool check)
186 {
187     OFCondition result = EC_Normal;
188     /* basic check of parameter */
189     if (!session.empty())
190     {
191         /* check whether measurement group already exists */
192         if (!hasMeasurementGroup())
193             result = createMeasurementGroup();
194         /* TID 1411 (Volumetric ROI Measurements) Row 1b */
195         CHECK_RESULT(addOrReplaceContentItem(ACTIVITY_SESSION, RT_hasObsContext, VT_Text, CODE_NCIt_ActivitySession, "TID 1411 - Row 1b", check));
196         CHECK_RESULT(getCurrentContentItem().setStringValue(session, check));
197     } else
198         result = EC_IllegalParameter;
199     return result;
200 }
201 
202 
203 template<typename T1, typename T2, typename T3, typename T4>
setTrackingIdentifier(const OFString & trackingID,const OFBool check)204 OFCondition TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::setTrackingIdentifier(const OFString &trackingID,
205                                                                                      const OFBool check)
206 {
207     OFCondition result = EC_Normal;
208     /* basic check of parameter */
209     if (!trackingID.empty())
210     {
211         /* check whether measurement group already exists */
212         if (!hasMeasurementGroup())
213             result = createMeasurementGroup();
214         /* TID 1411 (Volumetric ROI Measurements) Row 2 */
215         CHECK_RESULT(addOrReplaceContentItem(TRACKING_IDENTIFIER, RT_hasObsContext, VT_Text, CODE_DCM_TrackingIdentifier, "TID 1411 - Row 2", check));
216         CHECK_RESULT(getCurrentContentItem().setStringValue(trackingID, check));
217     } else
218         result = EC_IllegalParameter;
219     return result;
220 }
221 
222 
223 template<typename T1, typename T2, typename T3, typename T4>
setTrackingUniqueIdentifier(const OFString & trackingUID,const OFBool check)224 OFCondition TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::setTrackingUniqueIdentifier(const OFString &trackingUID,
225                                                                                            const OFBool check)
226 {
227     OFCondition result = EC_Normal;
228     /* basic check of parameter */
229     if (!trackingUID.empty())
230     {
231         /* check whether measurement group already exists */
232         if (!hasMeasurementGroup())
233             result = createMeasurementGroup();
234         /* TID 1411 (Volumetric ROI Measurements) Row 3 */
235         CHECK_RESULT(addOrReplaceContentItem(TRACKING_UNIQUE_IDENTIFIER, RT_hasObsContext, VT_UIDRef, CODE_DCM_TrackingUniqueIdentifier, "TID 1411 - Row 3", check));
236         CHECK_RESULT(getCurrentContentItem().setStringValue(trackingUID, check));
237     } else
238         result = EC_IllegalParameter;
239     return result;
240 }
241 
242 
243 template<typename T1, typename T2, typename T3, typename T4>
setFinding(const DSRCodedEntryValue & finding,const OFBool check)244 OFCondition TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::setFinding(const DSRCodedEntryValue &finding,
245                                                                           const OFBool check)
246 {
247     OFCondition result = EC_Normal;
248     /* basic check of parameter */
249     if (finding.isComplete())
250     {
251         /* check whether measurement group already exists */
252         if (!hasMeasurementGroup())
253             result = createMeasurementGroup();
254         /* TID 1411 (Volumetric ROI Measurements) Row 3b */
255         CHECK_RESULT(addOrReplaceContentItem(FINDING, RT_contains, VT_Code, CODE_DCM_Finding, "TID 1411 - Row 3b", check));
256         CHECK_RESULT(getCurrentContentItem().setCodeValue(finding, check));
257     } else
258         result = EC_IllegalParameter;
259     return result;
260 }
261 
262 
263 template<typename T1, typename T2, typename T3, typename T4>
setTimePoint(const OFString & timePoint,const OFBool check)264 OFCondition TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::setTimePoint(const OFString &timePoint,
265                                                                             const OFBool check)
266 {
267     OFCondition result = EC_Normal;
268     /* basic check of parameter */
269     if (!timePoint.empty())
270     {
271         /* check whether measurement group already exists */
272         if (!hasMeasurementGroup())
273             result = createMeasurementGroup();
274         /* TID 1502 (Time Point Context) Row 3 */
275         CHECK_RESULT(addOrReplaceContentItem(TIME_POINT, RT_hasObsContext, VT_Text, CODE_UMLS_TimePoint, "TID 1502 - Row 3", check));
276         CHECK_RESULT(getCurrentContentItem().setStringValue(timePoint, check));
277     } else
278         result = EC_IllegalParameter;
279     return result;
280 }
281 
282 
283 template<typename T1, typename T2, typename T3, typename T4>
setReferencedSegment(const DSRImageReferenceValue & segment,const OFBool check)284 OFCondition TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::setReferencedSegment(const DSRImageReferenceValue &segment,
285                                                                                     const OFBool check)
286 {
287     OFCondition result = EC_Normal;
288     /* basic check of parameter */
289     if (segment.isComplete())
290     {
291         const char *annotationText = "TID 1411 - Row 7";
292         const DSRBasicCodedEntry conceptName(CODE_DCM_ReferencedSegment);
293         /* check for supported segmentation SOP classes */
294         if ((segment.getSOPClassUID() != UID_SegmentationStorage) && (segment.getSOPClassUID() != UID_SurfaceSegmentationStorage))
295         {
296             DCMSR_CMR_WARN("Cannot set value of '" << conceptName.CodeMeaning << "' content item (" << annotationText << ") ... wrong SOP Class");
297             DCMSR_CMR_DEBUG("SOP Class UID \"" << segment.getSOPClassUID() << "\" does not match one of the known Segmentation objects");
298             result = CMR_EC_InvalidSegmentationObject;
299         }
300         /*  ... and number of referenced segments */
301         else if ((segment.getSegmentList().getNumberOfItems() != 1))
302         {
303             DCMSR_CMR_WARN("Cannot set value of '" << conceptName.CodeMeaning << "' content item (" << annotationText << ") ... wrong number of segments");
304             result = CMR_EC_InvalidSegmentationObject;
305         } else {
306             /* check whether measurement group already exists */
307             if (!hasMeasurementGroup())
308                 result = createMeasurementGroup();
309             /* TID 1411 (Volumetric ROI Measurements) Row 7 */
310             CHECK_RESULT(addOrReplaceContentItem(REFERENCED_SEGMENT, RT_contains, VT_Image, conceptName, annotationText, check));
311             CHECK_RESULT(getCurrentContentItem().setImageReference(segment, check));
312         }
313     } else
314         result = EC_IllegalParameter;
315     return result;
316 }
317 
318 
319 template<typename T1, typename T2, typename T3, typename T4>
setReferencedSegment(DcmItem & dataset,const Uint16 segmentNumber,const OFBool copyTracking,const OFBool check)320 OFCondition TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::setReferencedSegment(DcmItem &dataset,
321                                                                                     const Uint16 segmentNumber,
322                                                                                     const OFBool copyTracking,
323                                                                                     const OFBool check)
324 {
325     DSRImageReferenceValue segment;
326     /* first, create the referenced image/segment object */
327     OFCondition result = segment.setReference(dataset, check);
328     segment.getSegmentList().addItem(segmentNumber);
329     /* then, add/set the corresponding content item */
330     CHECK_RESULT(setReferencedSegment(segment, check));
331     /* need to copy tracking information? (introduced with CP-1496) */
332     if (copyTracking && result.good())
333     {
334         DcmSequenceOfItems *dseq = NULL;
335         /* get SegmentSequence (should always be present) */
336         result = dataset.findAndGetSequence(DCM_SegmentSequence, dseq);
337         checkElementValue(dseq, DCM_SegmentSequence, "1-n", "1", result, "SegmentDescriptionMacro");
338         if (result.good())
339         {
340             DcmObject *dobj = NULL;
341             OFBool segmentFound = OFFalse;
342             /* iterate over all items in this sequence */
343             while (((dobj = dseq->nextInContainer(dobj)) != NULL) && !segmentFound)
344             {
345                 Uint16 number = 0;
346                 DcmItem *ditem = OFstatic_cast(DcmItem *, dobj);
347                 /* search for given segment number */
348                 if (ditem->findAndGetUint16(DCM_SegmentNumber, number).good())
349                 {
350                     if (segmentNumber == number)
351                     {
352                         OFString trackingID, trackingUID;
353                         /* get tracking ID and UID from current item (if present) and add/set content item */
354                         getAndCheckStringValueFromDataset(*ditem, DCM_TrackingID, trackingID, "1", "1C", "SegmentSequence");
355                         getAndCheckStringValueFromDataset(*ditem, DCM_TrackingUID, trackingUID, "1", "1C", "SegmentSequence");
356                         if (!trackingID.empty() && !trackingUID.empty())
357                         {
358                             CHECK_RESULT(setTrackingIdentifier(trackingID, check));
359                             CHECK_RESULT(setTrackingUniqueIdentifier(trackingUID, check));
360                         }
361                         else if (trackingID.empty() != trackingUID.empty())
362                         {
363                             /* report a violation of the type 1C conditions */
364                             DCMSR_CMR_WARN("Either Tracking ID or Tracking UID is absent/empty in referenced segmentation object");
365                         }
366                         /* given segment number found */
367                         segmentFound = OFTrue;
368                     }
369                 }
370             }
371             /* report a warning if referenced segment could not be found */
372             if (!segmentFound)
373             {
374                 DCMSR_CMR_WARN("Cannot copy tracking information for '" << CODE_DCM_ReferencedSegment.CodeMeaning << "' content item (TID 1411 - Row 7) ... segment not found");
375                 DCMSR_CMR_DEBUG("Cannot find given Segment Number (" << segmentNumber << ") in Segment Sequence of referenced segmentation object");
376             }
377         } else {
378             /* report a warning if referenced segment could not be found */
379             DCMSR_CMR_WARN("Cannot copy tracking information for '" << CODE_DCM_ReferencedSegment.CodeMeaning << "' content item (TID 1411 - Row 7) ... segment not found");
380         }
381         /* tbc: return with an error in case the tracking information could not be copied? */
382     }
383     return result;
384 }
385 
386 
387 template<typename T1, typename T2, typename T3, typename T4>
setSourceSeriesForSegmentation(const OFString & seriesUID,const OFBool check)388 OFCondition TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::setSourceSeriesForSegmentation(const OFString &seriesUID,
389                                                                                               const OFBool check)
390 {
391     OFCondition result = EC_Normal;
392     /* basic check of parameter */
393     if (!seriesUID.empty())
394     {
395         /* check whether measurement group already exists */
396         if (!hasMeasurementGroup())
397             result = createMeasurementGroup();
398         /* TID 1411 (Volumetric ROI Measurements) Row 12 */
399         CHECK_RESULT(addOrReplaceContentItem(SOURCE_SERIES_FOR_SEGMENTATION, RT_contains, VT_UIDRef, CODE_DCM_SourceSeriesForSegmentation, "TID 1411 - Row 12", check));
400         CHECK_RESULT(getCurrentContentItem().setStringValue(seriesUID, check));
401     } else
402         result = EC_IllegalParameter;
403     return result;
404 }
405 
406 
407 template<typename T1, typename T2, typename T3, typename T4>
setRealWorldValueMap(const DSRCompositeReferenceValue & valueMap,const OFBool check)408 OFCondition TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::setRealWorldValueMap(const DSRCompositeReferenceValue &valueMap,
409                                                                                     const OFBool check)
410 {
411     OFCondition result = EC_Normal;
412     /* basic check of parameter */
413     if (valueMap.isComplete())
414     {
415         const char *annotationText = "TID 1411 - Row 14";
416         const DSRBasicCodedEntry conceptName(CODE_DCM_RealWorldValueMapUsedForMeasurement);
417         /* check for real world value mapping SOP classes */
418         if (valueMap.getSOPClassUID() != UID_RealWorldValueMappingStorage)
419         {
420             DCMSR_CMR_WARN("Cannot set value of '" << conceptName.CodeMeaning << "' content item (" << annotationText << ") ... wrong SOP Class");
421             DCMSR_CMR_DEBUG("SOP Class UID \"" << valueMap.getSOPClassUID() << "\" does not match the one of the Real World Value Mapping object");
422             result = CMR_EC_InvalidRealWorldValueMappingObject;
423         } else {
424             /* check whether measurement group already exists */
425             if (!hasMeasurementGroup())
426                 result = createMeasurementGroup();
427             /* TID 1411 (Volumetric ROI Measurements) Row 14 */
428             CHECK_RESULT(addOrReplaceContentItem(REAL_WORLD_VALUE_MAP, RT_contains, VT_Composite, conceptName, annotationText, check));
429             CHECK_RESULT(getCurrentContentItem().setCompositeReference(valueMap, check));
430         }
431     } else
432         result = EC_IllegalParameter;
433     return result;
434 }
435 
436 
437 template<typename T1, typename T2, typename T3, typename T4>
setRealWorldValueMap(DcmItem & dataset,const OFBool check)438 OFCondition TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::setRealWorldValueMap(DcmItem &dataset,
439                                                                                     const OFBool check)
440 {
441     DSRCompositeReferenceValue valueMap;
442     /* first, create the referenced composite object */
443     OFCondition result = valueMap.setReference(dataset, check);
444     /* then, add/set the corresponding content item */
445     CHECK_RESULT(setRealWorldValueMap(valueMap, check));
446     return result;
447 }
448 
449 
450 template<typename T1, typename T2, typename T_Method, typename T4>
setMeasurementMethod(const T_Method & method,const OFBool check)451 OFCondition TID1411_VolumetricROIMeasurements<T1, T2, T_Method, T4>::setMeasurementMethod(const T_Method &method,
452                                                                                           const OFBool check)
453 {
454     OFCondition result = EC_Normal;
455     /* basic check of parameter */
456     if (method.hasSelectedValue())
457     {
458         /* check whether measurement group already exists */
459         if (!hasMeasurementGroup())
460             result = createMeasurementGroup();
461         /* TID 1419 (ROI Measurements) Row 1 */
462         CHECK_RESULT(addOrReplaceContentItem(MEASUREMENT_METHOD, RT_hasConceptMod, VT_Code, CODE_SCT_MeasurementMethod, "TID 1419 - Row 1", check));
463         CHECK_RESULT(getCurrentContentItem().setCodeValue(method, check));
464     } else
465         result = EC_IllegalParameter;
466     return result;
467 }
468 
469 
470 template<typename T1, typename T2, typename T3, typename T4>
addFindingSite(const DSRCodedEntryValue & site,const CID244e_Laterality & laterality,const DSRCodedEntryValue & siteModifier,const OFBool check)471 OFCondition TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::addFindingSite(const DSRCodedEntryValue &site,
472                                                                               const CID244e_Laterality &laterality,
473                                                                               const DSRCodedEntryValue &siteModifier,
474                                                                               const OFBool check)
475 {
476     OFCondition result = EC_Normal;
477     /* basic check of mandatory parameter */
478     if (site.isComplete())
479     {
480         /* check whether measurement group already exists */
481         if (!hasMeasurementGroup())
482             result = createMeasurementGroup();
483         if (result.good())
484         {
485             /* create a new subtree in order to "rollback" in case of error */
486             DSRDocumentSubTree *subTree = new DSRDocumentSubTree;
487             if (subTree != NULL)
488             {
489                 /* TID 1419 (ROI Measurements) Row 2 */
490                 CHECK_RESULT(subTree->addContentItem(RT_hasConceptMod, VT_Code, CODE_SCT_FindingSite, check));
491                 CHECK_RESULT(subTree->getCurrentContentItem().setCodeValue(site, check));
492                 CHECK_RESULT(subTree->getCurrentContentItem().setAnnotationText("TID 1419 - Row 2"));
493                 const size_t lastNode = subTree->getNodeID();
494                 /* TID 1419 (ROI Measurements) Row 3 - optional */
495                 if (laterality.hasSelectedValue())
496                 {
497                     CHECK_RESULT(subTree->addChildContentItem(RT_hasConceptMod, VT_Code, CODE_SCT_Laterality, check));
498                     CHECK_RESULT(subTree->getCurrentContentItem().setCodeValue(laterality, check));
499                     CHECK_RESULT(subTree->getCurrentContentItem().setAnnotationText("TID 1419 - Row 3"));
500                     GOOD_RESULT(subTree->gotoParent());
501                 }
502                 /* TID 1419 (ROI Measurements) Row 4 - optional */
503                 if (siteModifier.isComplete())
504                 {
505                     CHECK_RESULT(subTree->addChildContentItem(RT_hasConceptMod, VT_Code, CODE_SCT_TopographicalModifier, check));
506                     CHECK_RESULT(subTree->getCurrentContentItem().setCodeValue(siteModifier, check));
507                     CHECK_RESULT(subTree->getCurrentContentItem().setAnnotationText("TID 1419 - Row 4"));
508                     GOOD_RESULT(subTree->gotoParent());
509                 }
510                 /* if everything was OK, insert new subtree into the template */
511                 if (result.good() && !subTree->isEmpty())
512                 {
513                     /* go to last measurement (if any) */
514                     if (gotoLastEntryFromNodeList(this, LAST_FINDING_SITE) == getEntryFromNodeList(MEASUREMENT_GROUP))
515                     {
516                         /* insert subtree below root */
517                         STORE_RESULT(insertSubTree(subTree, AM_belowCurrent));
518                     } else  {
519                         /* insert subtree after current position */
520                         STORE_RESULT(insertSubTree(subTree, AM_afterCurrent));
521                     }
522                     /* store ID of recently added node for later use */
523                     GOOD_RESULT(storeEntryInNodeList(LAST_FINDING_SITE, lastNode));
524                     /* in case of error, make sure that memory is freed */
525                     BAD_RESULT(delete subTree);
526                 } else {
527                     /* delete the new subtree since it has not been inserted */
528                     delete subTree;
529                 }
530             } else
531                 result = EC_MemoryExhausted;
532         } else
533             result = CMR_EC_NoMeasurement;
534     } else
535         result = EC_IllegalParameter;
536     return result;
537 }
538 
539 
540 template<typename T_Measurement, typename T_Units, typename T_Method, typename T_Derivation>
addMeasurement(const T_Measurement & conceptName,const MeasurementValue & numericValue,const OFBool checkEmpty,const OFBool checkValue)541 OFCondition TID1411_VolumetricROIMeasurements<T_Measurement, T_Units, T_Method, T_Derivation>::addMeasurement(const T_Measurement &conceptName,
542                                                                                                               const MeasurementValue &numericValue,
543                                                                                                               const OFBool checkEmpty,
544                                                                                                               const OFBool checkValue)
545 {
546     OFCondition result = EC_Normal;
547     /* basic check of mandatory parameters */
548     if (conceptName.hasSelectedValue() && numericValue.isComplete())
549     {
550         /* check whether measurement group already exists */
551         if (!hasMeasurementGroup())
552             result = createMeasurementGroup();
553         if (result.good())
554         {
555             /* go to content item at TID 1411 (Volumetric ROI Measurements) Row 15 */
556             if (gotoEntryFromNodeList(this, LAST_MEASUREMENT) > 0)
557             {
558                 /* check whether the current instance of TID 1419 is non-empty (if needed) */
559                 if (checkEmpty && Measurement->isEmpty())
560                     result = getMeasurement().createNewMeasurement(conceptName, numericValue, checkValue);
561                 else {
562                     /* create new instance of TID 1419 (ROI Measurements) */
563                     TID1419_Measurement *subTempl = new TID1419_Measurement(conceptName, numericValue, checkValue);
564                     if (subTempl != NULL)
565                     {
566                         /* store (shared) reference to new instance */
567                         Measurement.reset(subTempl);
568                         /* and add it to the current template (TID 1411 - Row 15) */
569                         STORE_RESULT(includeTemplate(Measurement, AM_afterCurrent, RT_contains));
570                         CHECK_RESULT(getCurrentContentItem().setAnnotationText("TID 1411 - Row 15"));
571                         GOOD_RESULT(storeEntryInNodeList(LAST_MEASUREMENT, getNodeID()));
572                         /* tbc: what if the call of includeTemplate() fails? */
573                     } else
574                         result = EC_MemoryExhausted;
575                 }
576             } else
577                 result = CMR_EC_NoMeasurementGroup;
578         }
579     } else
580         result = EC_IllegalParameter;
581     return result;
582 }
583 
584 
585 template<typename T1, typename T2, typename T3, typename T4>
addQualitativeEvaluation(const DSRCodedEntryValue & conceptName,const DSRCodedEntryValue & codeValue,const OFBool check)586 OFCondition TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::addQualitativeEvaluation(const DSRCodedEntryValue &conceptName,
587                                                                                         const DSRCodedEntryValue &codeValue,
588                                                                                         const OFBool check)
589 {
590     OFCondition result = EC_Normal;
591     /* make sure that the parameters are non-empty */
592     if (conceptName.isComplete() && codeValue.isComplete())
593     {
594         /* check whether measurement group already exists */
595         if (!hasMeasurementGroup())
596             result = createMeasurementGroup();
597         if (result.good())
598         {
599             /* go to last qualitative evaluation (if any) */
600             if (gotoLastEntryFromNodeList(this, LAST_QUALITATIVE_EVALUATION) == getEntryFromNodeList(MEASUREMENT_GROUP))
601             {
602                 /* insert TID 1411 (Volumetric ROI Measurements) Row 16 below root */
603                 STORE_RESULT(addChildContentItem(RT_contains, VT_Code, conceptName, check));
604             } else {
605                /* insert TID 1411 (Volumetric ROI Measurements) Row 16 after current position */
606                 STORE_RESULT(addContentItem(RT_contains, VT_Code, conceptName, check));
607             }
608             CHECK_RESULT(getCurrentContentItem().setCodeValue(codeValue, check));
609             CHECK_RESULT(getCurrentContentItem().setAnnotationText("TID 1411 - Row 16"));
610             /* store ID of recently added node for later use */
611             GOOD_RESULT(storeEntryInNodeList(LAST_QUALITATIVE_EVALUATION, getNodeID()));
612         }
613     } else
614         result = EC_IllegalParameter;
615     return result;
616 }
617 
618 
619 template<typename T1, typename T2, typename T3, typename T4>
addQualitativeEvaluation(const DSRCodedEntryValue & conceptName,const OFString & stringValue,const OFBool check)620 OFCondition TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::addQualitativeEvaluation(const DSRCodedEntryValue &conceptName,
621                                                                                         const OFString &stringValue,
622                                                                                         const OFBool check)
623 {
624     OFCondition result = EC_Normal;
625     /* make sure that the parameters are non-empty */
626     if (conceptName.isComplete() && !stringValue.empty())
627     {
628         /* check whether measurement group already exists */
629         if (!hasMeasurementGroup())
630             result = createMeasurementGroup();
631         if (result.good())
632         {
633             /* go to last qualitative evaluation (if any) */
634             if (gotoLastEntryFromNodeList(this, LAST_QUALITATIVE_EVALUATION) == getEntryFromNodeList(MEASUREMENT_GROUP))
635             {
636                 /* insert TID 1411 (Volumetric ROI Measurements) Row 17 below root */
637                 STORE_RESULT(addChildContentItem(RT_contains, VT_Text, conceptName, check));
638             } else {
639                /* insert TID 1411 (Volumetric ROI Measurements) Row 17 after current position */
640                 STORE_RESULT(addContentItem(RT_contains, VT_Text, conceptName, check));
641             }
642             CHECK_RESULT(getCurrentContentItem().setStringValue(stringValue, check));
643             CHECK_RESULT(getCurrentContentItem().setAnnotationText("TID 1411 - Row 17"));
644             /* store ID of recently added node for later use */
645             GOOD_RESULT(storeEntryInNodeList(LAST_QUALITATIVE_EVALUATION, getNodeID()));
646         }
647     } else
648         result = EC_IllegalParameter;
649     return result;
650 }
651 
652 
653 // protected methods
654 
655 template<typename T1, typename T2, typename T3, typename T4>
createMeasurementGroup()656 OFCondition TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::createMeasurementGroup()
657 {
658     OFCondition result = SR_EC_InvalidTemplateStructure;
659     if (isEmpty())
660     {
661         /* TID 1411 (Volumetric ROI Measurements) Row 1 */
662         STORE_RESULT(addContentItem(RT_unknown, VT_Container, CODE_DCM_MeasurementGroup));
663         CHECK_RESULT(getCurrentContentItem().setAnnotationText("TID 1411 - Row 1"));
664         GOOD_RESULT(storeEntryInNodeList(MEASUREMENT_GROUP, getNodeID()));
665         /* TID 1411 (Volumetric ROI Measurements) Row 15 */
666         CHECK_RESULT(includeTemplate(Measurement, AM_belowCurrent, RT_contains));
667         CHECK_RESULT(getCurrentContentItem().setAnnotationText("TID 1411 - Row 15"));
668         GOOD_RESULT(storeEntryInNodeList(LAST_MEASUREMENT, getNodeID()));
669         /* if anything went wrong, clear the report */
670         BAD_RESULT(clear());
671     }
672     return result;
673 }
674 
675 
676 template<typename T1, typename T2, typename T3, typename T4>
addOrReplaceContentItem(const size_t nodePos,const E_RelationshipType relationshipType,const E_ValueType valueType,const DSRCodedEntryValue & conceptName,const OFString & annotationText,const OFBool check)677 OFCondition TID1411_VolumetricROIMeasurements<T1, T2, T3, T4>::addOrReplaceContentItem(const size_t nodePos,
678                                                                                        const E_RelationshipType relationshipType,
679                                                                                        const E_ValueType valueType,
680                                                                                        const DSRCodedEntryValue &conceptName,
681                                                                                        const OFString &annotationText,
682                                                                                        const OFBool check)
683 {
684     OFCondition result = EC_Normal;
685     /* check concept name and coded entry value */
686     if (conceptName.isComplete())
687     {
688         /* check whether content item already exists */
689         if (getEntryFromNodeList(nodePos) == 0)
690         {
691             /* if not, create the content item (at correct position) */
692             if (gotoLastEntryFromNodeList(this, nodePos) == getEntryFromNodeList(MEASUREMENT_GROUP))
693             {
694                 /* need to add the new content item as the first child */
695                 if (addContentItem(relationshipType, valueType, AM_belowCurrentBeforeFirstChild) > 0)
696                 {
697                     if (getCurrentContentItem().setConceptName(conceptName, check).bad())
698                         result = SR_EC_InvalidConceptName;
699                 } else
700                     result = SR_EC_CannotAddContentItem;
701 
702             } else {
703                 /* add new content item as a sibling (after the current one) */
704                 STORE_RESULT(addContentItem(relationshipType, valueType, conceptName));
705             }
706             /* store ID of added node for later use */
707             GOOD_RESULT(storeEntryInNodeList(nodePos, getNodeID()));
708         }
709         else if (gotoEntryFromNodeList(this, nodePos) > 0)
710         {
711             /* make sure that the value type of the existing content item is correct */
712             if (getCurrentContentItem().getValueType() != valueType)
713             {
714                 DCMSR_CMR_WARN("Cannot replace value of '" << conceptName.getCodeMeaning()
715                     << "' content item (" << annotationText << ") ... wrong value type");
716                 result = SR_EC_InvalidContentItem;
717             }
718             else if (getCurrentContentItem().getConceptName() != conceptName)
719             {
720                 DCMSR_CMR_WARN("Cannot replace value of '" << conceptName.getCodeMeaning()
721                     << "' content item (" << annotationText << ") ... wrong concept name");
722                 result = SR_EC_InvalidConceptName;
723             } else {
724                 DCMSR_CMR_DEBUG("Replacing value of '" << conceptName.getCodeMeaning()
725                     << "' content item (" << annotationText << ")");
726                 /* the actual replacing of the value is done by the caller of this method */
727             }
728         } else
729             result = SR_EC_InvalidTemplateStructure;
730         /* finally, set annotation */
731         CHECK_RESULT(getCurrentContentItem().setAnnotationText(annotationText));
732     } else
733         result = SR_EC_InvalidConceptName;
734     return result;
735 }
736 
737 
738 // explicit template instantiation (needed for use in TID 1500)
739 template class TID1411_VolumetricROIMeasurements<CID7469_GenericIntensityAndSizeMeasurements,
740                                                  CID7181_AbstractMultiDimensionalImageModelComponentUnits,
741                                                  CID6147_ResponseCriteria,
742                                                  CID7464_GeneralRegionOfInterestMeasurementModifiers>;
743