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