1 /*
2  *
3  *  Copyright (C) 2015-2017, J. Riesmeier, Oldenburg, Germany
4  *  All rights reserved.  See COPYRIGHT file for details.
5  *
6  *  Source file for class TID1500_MeasurementReport
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/tid1500.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/umls.h"
20 #include "dcmtk/dcmsr/dsrtpltn.h"
21 
22 #include "dcmtk/dcmdata/dcuid.h"
23 
24 
25 // helper macros for checking the return value of API calls
26 #define CHECK_RESULT(call) if (result.good()) result = call
27 #define STORE_RESULT(call) result = call
28 #define GOOD_RESULT(call) if (result.good()) call
29 #define BAD_RESULT(call) if (result.bad()) call
30 
31 // index positions in node list (makes source code more readable)
32 #define MEASUREMENT_REPORT               0
33 #define OBSERVATION_CONTEXT              1
34 #define LAST_PROCEDURE_REPORTED          2
35 #define IMAGING_MEASUREMENTS             3
36 #define LAST_VOLUMETRIC_ROI_MEASUREMENTS 4
37 #define LAST_MEASUREMENT_GROUP           5
38 #define QUALITATIVE_EVALUATIONS          6
39 #define NUMBER_OF_LIST_ENTRIES           7
40 
41 // general information on TID 1500 (Measurement Report)
42 #define TEMPLATE_NUMBER      "1500"
43 #define MAPPING_RESOURCE     "DCMR"
44 #define MAPPING_RESOURCE_UID UID_DICOMContentMappingResource
45 #define TEMPLATE_TYPE        OFTrue   /* extensible */
46 #define TEMPLATE_ORDER       OFFalse  /* non-significant */
47 
48 
TID1500_MeasurementReport(const CID7021_MeasurementReportDocumentTitles & title,const OFBool check)49 TID1500_MeasurementReport::TID1500_MeasurementReport(const CID7021_MeasurementReportDocumentTitles &title,
50                                                      const OFBool check)
51   : DSRRootTemplate(DT_EnhancedSR, TEMPLATE_NUMBER, MAPPING_RESOURCE, MAPPING_RESOURCE_UID),
52     Language(new TID1204_LanguageOfContentItemAndDescendants()),
53     ObservationContext(new TID1001_ObservationContext()),
54     ImageLibrary(new TID1600_ImageLibrary()),
55     VolumetricROIMeasurements(new TID1411_Measurements()),
56     MeasurementGroup(new TID1501_Measurements())
57 {
58     setExtensible(TEMPLATE_TYPE);
59     setOrderSignificant(TEMPLATE_ORDER);
60     /* need to store position of various content items */
61     reserveEntriesInNodeList(NUMBER_OF_LIST_ENTRIES, OFTrue /*initialize*/);
62     /* if specified, create an initial report */
63     if (title.hasSelectedValue())
64         createMeasurementReport(title, check);
65 }
66 
67 
clear()68 void TID1500_MeasurementReport::clear()
69 {
70     DSRRootTemplate::clear();
71     Language->clear();
72     ObservationContext->clear();
73     ImageLibrary->clear();
74     VolumetricROIMeasurements->clear();
75     MeasurementGroup->clear();
76 }
77 
78 
isValid() const79 OFBool TID1500_MeasurementReport::isValid() const
80 {
81     /* check whether base class is valid and all required content items are present */
82     return DSRRootTemplate::isValid() &&
83         Language->isValid() && ObservationContext->isValid() && ImageLibrary->isValid() &&
84         hasProcedureReported() && (hasImagingMeasurements() || hasQualitativeEvaluations()) &&
85         (VolumetricROIMeasurements->isEmpty() || VolumetricROIMeasurements->isValid()) &&
86         (MeasurementGroup->isEmpty() || MeasurementGroup->isValid());
87 }
88 
89 
hasProcedureReported() const90 OFBool TID1500_MeasurementReport::hasProcedureReported() const
91 {
92     /* check for content item at TID 1500 (Measurement Report) Row 4 */
93     return (getEntryFromNodeList(LAST_PROCEDURE_REPORTED) > 0);
94 }
95 
96 
hasImagingMeasurements(const OFBool checkChildren) const97 OFBool TID1500_MeasurementReport::hasImagingMeasurements(const OFBool checkChildren) const
98 {
99     OFBool result = OFFalse;
100     /* need to check for child nodes? */
101     if (checkChildren)
102     {
103         DSRDocumentTreeNodeCursor cursor(getRoot());
104         /* go to content item at TID 1500 (Measurement Report) Row 6 */
105         if (gotoEntryFromNodeList(cursor, IMAGING_MEASUREMENTS) > 0)
106         {
107             /* check whether any of the "included templates" is non-empty */
108             if (cursor.gotoChild())
109             {
110                 while (cursor.isValid() && (cursor.getNode()->getValueType() == VT_includedTemplate))
111                 {
112                     const DSRSubTemplate *subTempl = OFstatic_cast(const DSRIncludedTemplateTreeNode *, cursor.getNode())->getValue().get();
113                     if (subTempl != NULL)
114                     {
115                         result = !subTempl->isEmpty();
116                         if (result) break;
117                     }
118                     if (cursor.gotoNext() == 0)
119                     {
120                         /* invalidate cursor */
121                         cursor.clear();
122                     }
123                 }
124             }
125         }
126     } else {
127         /* check for content item at TID 1500 (Measurement Report) Row 6 */
128         result = (getEntryFromNodeList(IMAGING_MEASUREMENTS) > 0);
129     }
130     return result;
131 }
132 
133 
hasVolumetricROIMeasurements(const OFBool checkChildren) const134 OFBool TID1500_MeasurementReport::hasVolumetricROIMeasurements(const OFBool checkChildren) const
135 {
136     OFBool result = OFFalse;
137     /* need to check for child nodes? */
138     if (checkChildren)
139     {
140         DSRDocumentTreeNodeCursor cursor(getRoot());
141         /* go to content item at TID 1500 (Measurement Report) Row 8 */
142         if (gotoEntryFromNodeList(cursor, LAST_VOLUMETRIC_ROI_MEASUREMENTS) > 0)
143         {
144             /* check whether any of the "included TID 1411 templates" is non-empty */
145             while (cursor.isValid() && (cursor.getNode()->getValueType() == VT_includedTemplate))
146             {
147                 const DSRSubTemplate *subTempl = OFstatic_cast(const DSRIncludedTemplateTreeNode *, cursor.getNode())->getValue().get();
148                 if (subTempl != NULL)
149                 {
150                     if (subTempl->compareTemplateIdentication("1411", "DCMR"))
151                     {
152                         result = !subTempl->isEmpty();
153                         if (result) break;
154                     } else {
155                         /* exit loop */
156                         break;
157                     }
158                 }
159                 if (cursor.gotoPrevious() == 0)
160                 {
161                     /* invalidate cursor */
162                     cursor.clear();
163                 }
164             }
165         }
166     } else {
167         /* check for content item at TID 1500 (Measurement Report) Row 8 */
168         result = (getEntryFromNodeList(LAST_VOLUMETRIC_ROI_MEASUREMENTS) > 0);
169     }
170     return result;
171 }
172 
173 
hasIndividualMeasurements(const OFBool checkChildren) const174 OFBool TID1500_MeasurementReport::hasIndividualMeasurements(const OFBool checkChildren) const
175 {
176     OFBool result = OFFalse;
177     /* need to check for child nodes? */
178     if (checkChildren)
179     {
180         DSRDocumentTreeNodeCursor cursor(getRoot());
181         /* go to content item at TID 1500 (Measurement Report) Row 9 */
182         if (gotoEntryFromNodeList(cursor, LAST_MEASUREMENT_GROUP) > 0)
183         {
184             /* check whether any of the "included TID 1501 templates" is non-empty */
185             while (cursor.isValid() && (cursor.getNode()->getValueType() == VT_includedTemplate))
186             {
187                 const DSRSubTemplate *subTempl = OFstatic_cast(const DSRIncludedTemplateTreeNode *, cursor.getNode())->getValue().get();
188                 if (subTempl != NULL)
189                 {
190                     if (subTempl->compareTemplateIdentication("1501", "DCMR"))
191                     {
192                         result = !subTempl->isEmpty();
193                         if (result) break;
194                     } else {
195                         /* exit loop */
196                         break;
197                     }
198                 }
199                 if (cursor.gotoPrevious() == 0)
200                 {
201                     /* invalidate cursor */
202                     cursor.clear();
203                 }
204             }
205         }
206     } else {
207         /* check for content item at TID 1500 (Measurement Report) Row 9 */
208         result = (getEntryFromNodeList(LAST_MEASUREMENT_GROUP) > 0);
209     }
210     return result;
211 }
212 
213 
hasQualitativeEvaluations(const OFBool checkChildren) const214 OFBool TID1500_MeasurementReport::hasQualitativeEvaluations(const OFBool checkChildren) const
215 {
216     OFBool result = OFFalse;
217     /* need to check for child nodes? */
218     if (checkChildren)
219     {
220         DSRDocumentTreeNodeCursor cursor(getRoot());
221         /* go to content item at TID 1500 (Measurement Report) Row 12 */
222         if (gotoEntryFromNodeList(cursor, QUALITATIVE_EVALUATIONS) > 0)
223             result = cursor.hasChildNodes();
224     } else {
225         /* check for content item at TID 1500 (Measurement Report) Row 12 */
226         result = (getEntryFromNodeList(QUALITATIVE_EVALUATIONS) > 0);
227     }
228     return result;
229 }
230 
231 
getDocumentTitle(DSRCodedEntryValue & titleCode)232 OFCondition TID1500_MeasurementReport::getDocumentTitle(DSRCodedEntryValue &titleCode)
233 {
234     OFCondition result = EC_Normal;
235     /* go to content item at TID 1500 (Measurement Report) Row 1 */
236     if (gotoEntryFromNodeList(this, MEASUREMENT_REPORT) > 0)
237     {
238         titleCode = getCurrentContentItem().getConceptName();
239     } else {
240         /* in case of error, clear the result variable */
241         titleCode.clear();
242         result = SR_EC_ContentItemNotFound;
243     }
244     return result;
245 }
246 
247 
createNewMeasurementReport(const CID7021_MeasurementReportDocumentTitles & title,const OFBool check)248 OFCondition TID1500_MeasurementReport::createNewMeasurementReport(const CID7021_MeasurementReportDocumentTitles &title,
249                                                                   const OFBool check)
250 {
251     clear();
252     /* TID 1500 (Measurement Report) Row 1 */
253     return createMeasurementReport(title, check);
254 }
255 
256 
setLanguage(const CID5000_Languages & language,const CID5001_Countries & country,const OFBool check)257 OFCondition TID1500_MeasurementReport::setLanguage(const CID5000_Languages &language,
258                                                    const CID5001_Countries &country,
259                                                    const OFBool check)
260 {
261     /* TID 1500 (Measurement Report) Row 2 */
262     return getLanguage().setLanguage(language, country, check);
263 }
264 
265 
addProcedureReported(const CID100_QuantitativeDiagnosticImagingProcedures & procedure,const OFBool check)266 OFCondition TID1500_MeasurementReport::addProcedureReported(const CID100_QuantitativeDiagnosticImagingProcedures &procedure,
267                                                             const OFBool check)
268 {
269     OFCondition result = EC_IllegalParameter;
270     /* make sure that there is a coded entry */
271     if (procedure.hasSelectedValue())
272     {
273         /* go to last content item at TID 1500 (Measurement Report) Row 3 or 4 */
274         if (gotoLastEntryFromNodeList(this, LAST_PROCEDURE_REPORTED, OBSERVATION_CONTEXT) > 0)
275         {
276             /* TID 1500 (Measurement Report) Row 4 */
277             STORE_RESULT(addContentItem(RT_hasConceptMod, VT_Code, CODE_DCM_ProcedureReported, check));
278             CHECK_RESULT(getCurrentContentItem().setCodeValue(procedure, check));
279             CHECK_RESULT(getCurrentContentItem().setAnnotationText("TID 1500 - Row 4"));
280             /* store ID of recently added node for later use */
281             GOOD_RESULT(storeEntryInNodeList(LAST_PROCEDURE_REPORTED, getNodeID()));
282         } else
283             result = CMR_EC_NoMeasurementReport;
284     }
285     return result;
286 }
287 
288 
addVolumetricROIMeasurements(const OFBool checkEmpty)289 OFCondition TID1500_MeasurementReport::addVolumetricROIMeasurements(const OFBool checkEmpty)
290 {
291     OFCondition result = EC_Normal;
292     /* go to content item at TID 1500 (Measurement Report) Row 8 */
293     if (gotoEntryFromNodeList(this, LAST_VOLUMETRIC_ROI_MEASUREMENTS) > 0)
294     {
295         /* check whether the current instance of TID 1411 is non-empty (if needed) */
296         if (!checkEmpty || !VolumetricROIMeasurements->isEmpty())
297         {
298             /* create new instance of TID 1411 (Volumetric ROI Measurements) */
299             TID1411_Measurements *subTempl = new TID1411_Measurements();
300             if (subTempl != NULL)
301             {
302                 /* store (shared) reference to new instance */
303                 VolumetricROIMeasurements.reset(subTempl);
304                 /* and add it to the current template (TID 1500 - Row 8) */
305                 STORE_RESULT(includeTemplate(VolumetricROIMeasurements, AM_afterCurrent, RT_contains));
306                 CHECK_RESULT(getCurrentContentItem().setAnnotationText("TID 1500 - Row 8"));
307                 GOOD_RESULT(storeEntryInNodeList(LAST_VOLUMETRIC_ROI_MEASUREMENTS, getNodeID()));
308                 /* tbc: what if the call of includeTemplate() fails? */
309             } else
310                 result = EC_MemoryExhausted;
311         }
312     } else
313         result = CMR_EC_NoMeasurementReport;
314     return result;
315 }
316 
317 
addIndividualMeasurements(const OFBool checkEmpty)318 OFCondition TID1500_MeasurementReport::addIndividualMeasurements(const OFBool checkEmpty)
319 {
320     OFCondition result = EC_Normal;
321     /* go to content item at TID 1500 (Measurement Report) Row 9 */
322     if (gotoEntryFromNodeList(this, LAST_MEASUREMENT_GROUP) > 0)
323     {
324         /* check whether the current instance of TID 1501 is non-empty (if needed) */
325         if (!checkEmpty || !MeasurementGroup->isEmpty())
326         {
327             /* create new instance of TID 1501 (Measurement Group) */
328             TID1501_Measurements *subTempl = new TID1501_Measurements();
329             if (subTempl != NULL)
330             {
331                 /* store (shared) reference to new instance */
332                 MeasurementGroup.reset(subTempl);
333                 /* and add it to the current template (TID 1500 - Row 9) */
334                 STORE_RESULT(includeTemplate(MeasurementGroup, AM_afterCurrent, RT_contains));
335                 CHECK_RESULT(getCurrentContentItem().setAnnotationText("TID 1500 - Row 9"));
336                 GOOD_RESULT(storeEntryInNodeList(LAST_MEASUREMENT_GROUP, getNodeID()));
337                 /* tbc: what if the call of includeTemplate() fails? */
338             } else
339                 result = EC_MemoryExhausted;
340         }
341     } else
342         result = CMR_EC_NoMeasurementReport;
343     return result;
344 }
345 
346 
addQualitativeEvaluation(const DSRCodedEntryValue & conceptName,const DSRCodedEntryValue & codeValue,const OFBool check)347 OFCondition TID1500_MeasurementReport::addQualitativeEvaluation(const DSRCodedEntryValue &conceptName,
348                                                                 const DSRCodedEntryValue &codeValue,
349                                                                 const OFBool check)
350 {
351     OFCondition result = EC_IllegalParameter;
352     /* make sure that the parameters are non-empty */
353     if (conceptName.isComplete() && codeValue.isComplete())
354     {
355         /* create content item at TID 1500 (Measurement Report) Row 12 if not existing */
356         result = createQualitativeEvaluations();
357         /* go to content item at TID 1500 (Measurement Report) Row 12 */
358         if (gotoEntryFromNodeList(this, QUALITATIVE_EVALUATIONS) > 0)
359         {
360             /* TID 1500 (Measurement Report) Row 13 */
361             CHECK_RESULT(addChildContentItem(RT_contains, VT_Code, conceptName, check));
362             CHECK_RESULT(getCurrentContentItem().setCodeValue(codeValue, check));
363             CHECK_RESULT(getCurrentContentItem().setAnnotationText("TID 1500 - Row 13"));
364         } else
365             result = CMR_EC_NoMeasurementReport;
366     }
367     return result;
368 }
369 
370 
addQualitativeEvaluation(const DSRCodedEntryValue & conceptName,const OFString & stringValue,const OFBool check)371 OFCondition TID1500_MeasurementReport::addQualitativeEvaluation(const DSRCodedEntryValue &conceptName,
372                                                                 const OFString &stringValue,
373                                                                 const OFBool check)
374 {
375     OFCondition result = EC_IllegalParameter;
376     /* make sure that the parameters are non-empty */
377     if (conceptName.isComplete() && !stringValue.empty())
378     {
379         /* create content item at TID 1500 (Measurement Report) Row 12 if not existing */
380         result = createQualitativeEvaluations();
381         /* go to content item at TID 1500 (Measurement Report) Row 12 */
382         if (gotoEntryFromNodeList(this, QUALITATIVE_EVALUATIONS) > 0)
383         {
384             /* TID 1500 (Measurement Report) Row 14 */
385             CHECK_RESULT(addChildContentItem(RT_contains, VT_Text, conceptName, check));
386             CHECK_RESULT(getCurrentContentItem().setStringValue(stringValue, check));
387             CHECK_RESULT(getCurrentContentItem().setAnnotationText("TID 1500 - Row 14"));
388         } else
389             result = CMR_EC_NoMeasurementReport;
390     }
391     return result;
392 }
393 
394 
395 // protected methods
396 
createMeasurementReport(const CID7021_MeasurementReportDocumentTitles & title,const OFBool check)397 OFCondition TID1500_MeasurementReport::createMeasurementReport(const CID7021_MeasurementReportDocumentTitles &title,
398                                                                const OFBool check)
399 {
400     OFCondition result = EC_IllegalParameter;
401     /* make sure that there is a coded entry */
402     if (title.hasSelectedValue())
403     {
404         /* reassure that the report is definitely empty */
405         if (isEmpty())
406         {
407             /* TID 1500 (Measurement Report) Row 1 */
408             STORE_RESULT(addContentItem(RT_isRoot, VT_Container, title, check));
409             CHECK_RESULT(getCurrentContentItem().setAnnotationText("TID 1500 - Row 1"));
410             /* store ID of root node for later use */
411             GOOD_RESULT(storeEntryInNodeList(MEASUREMENT_REPORT, getNodeID()));
412             /* TID 1500 (Measurement Report) Row 2 */
413             CHECK_RESULT(includeTemplate(Language, AM_belowCurrent, RT_hasConceptMod));
414             CHECK_RESULT(getCurrentContentItem().setAnnotationText("TID 1500 - Row 2"));
415             /* TID 1500 (Measurement Report) Row 3 */
416             CHECK_RESULT(includeTemplate(ObservationContext, AM_afterCurrent, RT_hasObsContext));
417             CHECK_RESULT(getCurrentContentItem().setAnnotationText("TID 1500 - Row 3"));
418             GOOD_RESULT(storeEntryInNodeList(OBSERVATION_CONTEXT, getNodeID()));
419             /* TID 1500 (Measurement Report) Row 5 */
420             CHECK_RESULT(includeTemplate(ImageLibrary, AM_afterCurrent, RT_contains));
421             CHECK_RESULT(getCurrentContentItem().setAnnotationText("TID 1500 - Row 5"));
422             /* TID 1500 (Measurement Report) Row 6 */
423             CHECK_RESULT(addContentItem(RT_contains, VT_Container, CODE_DCM_ImagingMeasurements));
424             CHECK_RESULT(getCurrentContentItem().setAnnotationText("TID 1500 - Row 6"));
425             GOOD_RESULT(storeEntryInNodeList(IMAGING_MEASUREMENTS, getNodeID()));
426             /* TID 1500 (Measurement Report) Row 8 */
427             CHECK_RESULT(includeTemplate(VolumetricROIMeasurements, AM_belowCurrent, RT_contains));
428             CHECK_RESULT(getCurrentContentItem().setAnnotationText("TID 1500 - Row 8"));
429             GOOD_RESULT(storeEntryInNodeList(LAST_VOLUMETRIC_ROI_MEASUREMENTS, getNodeID()));
430             /* TID 1500 (Measurement Report) Row 9 */
431             CHECK_RESULT(includeTemplate(MeasurementGroup, AM_afterCurrent, RT_contains));
432             CHECK_RESULT(getCurrentContentItem().setAnnotationText("TID 1500 - Row 9"));
433             GOOD_RESULT(storeEntryInNodeList(LAST_MEASUREMENT_GROUP, getNodeID()));
434             /* if anything went wrong, clear the report */
435             BAD_RESULT(clear());
436         } else
437             result = SR_EC_InvalidTemplateStructure;
438     }
439     return result;
440 }
441 
442 
createQualitativeEvaluations()443 OFCondition TID1500_MeasurementReport::createQualitativeEvaluations()
444 {
445     OFCondition result = EC_Normal;
446     /* check whether content item at TID 1500 (Measurement Report) Row 12 already exists */
447     if (!hasQualitativeEvaluations())
448     {
449         /* if not, go to the preceding content item, which always exists */
450         if (gotoEntryFromNodeList(this, IMAGING_MEASUREMENTS) > 0)
451         {
452             /* ... and add TID 1500 (Measurement Report) Row 12 */
453             STORE_RESULT(addContentItem(RT_contains, VT_Container, CODE_UMLS_QualitativeEvaluations));
454             CHECK_RESULT(getCurrentContentItem().setAnnotationText("TID 1500 - Row 12"));
455             GOOD_RESULT(storeEntryInNodeList(QUALITATIVE_EVALUATIONS, getNodeID()));
456         } else
457             result = CMR_EC_NoMeasurementReport;
458     }
459     return result;
460 }
461