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