1 /*
2 *
3 * Copyright (C) 2000-2020, OFFIS e.V.
4 * All rights reserved. See COPYRIGHT file for details.
5 *
6 * This software and supporting documentation were developed by
7 *
8 * OFFIS e.V.
9 * R&D Division Health
10 * Escherweg 2
11 * D-26121 Oldenburg, Germany
12 *
13 *
14 * Module: dcmsr
15 *
16 * Author: Joerg Riesmeier
17 *
18 * Purpose:
19 * classes: DSRDocument
20 *
21 */
22
23
24 #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */
25
26 #include "dcmtk/dcmsr/dsrdoc.h"
27 #include "dcmtk/dcmsr/dsrxmld.h"
28 #include "dcmtk/dcmsr/dsrpnmtn.h"
29 #include "dcmtk/dcmsr/dsrdattn.h"
30 #include "dcmtk/dcmsr/dsrdtitn.h"
31 #include "dcmtk/dcmsr/dsrtimtn.h"
32
33 #include "dcmtk/dcmdata/dcdeftag.h"
34 #include "dcmtk/dcmdata/dcuid.h"
35 #include "dcmtk/dcmdata/dcvrdt.h"
36
37
38 /*---------------------*
39 * macro definitions *
40 *---------------------*/
41
42 #define DCMSR_PRINT_HEADER_FIELD_START(header_name, delimiter) \
43 DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_HEADER_NAME) \
44 stream << header_name; \
45 DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_DELIMITER) \
46 stream << delimiter; \
47 DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_HEADER_VALUE)
48
49 #define DCMSR_PRINT_HEADER_FIELD_END \
50 DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_RESET) \
51 stream << OFendl;
52
53
54 /*------------------*
55 * implementation *
56 *------------------*/
57
DSRDocument(const E_DocumentType documentType)58 DSRDocument::DSRDocument(const E_DocumentType documentType)
59 : DocumentTree(documentType),
60 FinalizedFlag(OFFalse),
61 PreliminaryFlagEnum(PF_invalid),
62 CompletionFlagEnum(CF_invalid),
63 VerificationFlagEnum(VF_invalid),
64 SpecificCharacterSetEnum(CS_default),
65 SOPClassUID(DCM_SOPClassUID),
66 SOPInstanceUID(DCM_SOPInstanceUID),
67 SpecificCharacterSet(DCM_SpecificCharacterSet),
68 InstanceCreationDate(DCM_InstanceCreationDate),
69 InstanceCreationTime(DCM_InstanceCreationTime),
70 InstanceCreatorUID(DCM_InstanceCreatorUID),
71 CodingSchemeIdentification(),
72 TimezoneOffsetFromUTC(DCM_TimezoneOffsetFromUTC),
73 StudyInstanceUID(DCM_StudyInstanceUID),
74 StudyDate(DCM_StudyDate),
75 StudyTime(DCM_StudyTime),
76 ReferringPhysicianName(DCM_ReferringPhysicianName),
77 StudyID(DCM_StudyID),
78 AccessionNumber(DCM_AccessionNumber),
79 StudyDescription(DCM_StudyDescription),
80 PatientName(DCM_PatientName),
81 PatientID(DCM_PatientID),
82 IssuerOfPatientID(DCM_IssuerOfPatientID),
83 PatientBirthDate(DCM_PatientBirthDate),
84 PatientSex(DCM_PatientSex),
85 Manufacturer(DCM_Manufacturer),
86 ManufacturerModelName(DCM_ManufacturerModelName),
87 DeviceSerialNumber(DCM_DeviceSerialNumber),
88 SoftwareVersions(DCM_SoftwareVersions),
89 SynchronizationFrameOfReferenceUID(DCM_SynchronizationFrameOfReferenceUID),
90 SynchronizationTrigger(DCM_SynchronizationTrigger),
91 AcquisitionTimeSynchronized(DCM_AcquisitionTimeSynchronized),
92 Modality(DCM_Modality),
93 SeriesInstanceUID(DCM_SeriesInstanceUID),
94 SeriesNumber(DCM_SeriesNumber),
95 SeriesDate(DCM_SeriesDate),
96 SeriesTime(DCM_SeriesTime),
97 ProtocolName(DCM_ProtocolName),
98 SeriesDescription(DCM_SeriesDescription),
99 ReferencedPerformedProcedureStep(DCM_ReferencedPerformedProcedureStepSequence),
100 InstanceNumber(DCM_InstanceNumber),
101 PreliminaryFlag(DCM_PreliminaryFlag),
102 CompletionFlag(DCM_CompletionFlag),
103 CompletionFlagDescription(DCM_CompletionFlagDescription),
104 VerificationFlag(DCM_VerificationFlag),
105 ContentDate(DCM_ContentDate),
106 ContentTime(DCM_ContentTime),
107 VerifyingObserver(DCM_VerifyingObserverSequence),
108 PredecessorDocuments(DCM_PredecessorDocumentsSequence),
109 IdenticalDocuments(DCM_IdenticalDocumentsSequence),
110 PerformedProcedureCode(DCM_PerformedProcedureCodeSequence),
111 CurrentRequestedProcedureEvidence(DCM_CurrentRequestedProcedureEvidenceSequence),
112 PertinentOtherEvidence(DCM_PertinentOtherEvidenceSequence),
113 ReferencedInstances()
114 {
115 DCMSR_DEBUG("Initializing all DICOM header attributes");
116 /* set initial values for a new SOP instance */
117 updateAttributes(OFTrue /*updateAll*/, OFFalse /*verboseMode*/);
118 }
119
120
~DSRDocument()121 DSRDocument::~DSRDocument()
122 {
123 }
124
125
clear()126 void DSRDocument::clear()
127 {
128 /* clear SR document tree */
129 DocumentTree.clear();
130 FinalizedFlag = OFFalse;
131 /* clear enumerated values */
132 PreliminaryFlagEnum = PF_invalid;
133 CompletionFlagEnum = CF_invalid;
134 VerificationFlagEnum = VF_invalid;
135 SpecificCharacterSetEnum = CS_default;
136 /* clear all DICOM attributes */
137 SOPClassUID.clear();
138 SOPInstanceUID.clear();
139 SpecificCharacterSet.clear();
140 InstanceCreationDate.clear();
141 InstanceCreationTime.clear();
142 InstanceCreatorUID.clear();
143 CodingSchemeIdentification.clear();
144 TimezoneOffsetFromUTC.clear();
145 StudyInstanceUID.clear();
146 StudyDate.clear();
147 StudyTime.clear();
148 ReferringPhysicianName.clear();
149 StudyID.clear();
150 AccessionNumber.clear();
151 StudyDescription.clear();
152 PatientName.clear();
153 PatientID.clear();
154 IssuerOfPatientID.clear();
155 PatientBirthDate.clear();
156 PatientSex.clear();
157 Manufacturer.clear();
158 ManufacturerModelName.clear();
159 DeviceSerialNumber.clear();
160 SoftwareVersions.clear();
161 SynchronizationFrameOfReferenceUID.clear();
162 SynchronizationTrigger.clear();
163 AcquisitionTimeSynchronized.clear();
164 Modality.clear();
165 SeriesInstanceUID.clear();
166 SeriesNumber.clear();
167 SeriesDate.clear();
168 SeriesTime.clear();
169 ProtocolName.clear();
170 SeriesDescription.clear();
171 ReferencedPerformedProcedureStep.clear();
172 InstanceNumber.clear();
173 PreliminaryFlag.clear();
174 CompletionFlag.clear();
175 CompletionFlagDescription.clear();
176 VerificationFlag.clear();
177 ContentDate.clear();
178 ContentTime.clear();
179 VerifyingObserver.clear();
180 PerformedProcedureCode.clear();
181 /* clear list structures */
182 PredecessorDocuments.clear();
183 IdenticalDocuments.clear();
184 CurrentRequestedProcedureEvidence.clear();
185 PertinentOtherEvidence.clear();
186 ReferencedInstances.clear();
187 }
188
189
isValid()190 OFBool DSRDocument::isValid()
191 {
192 /* document is valid if the document tree is valid and ... */
193 return DocumentTree.isValid() && !SOPClassUID.isEmpty() && !SOPInstanceUID.isEmpty();
194 }
195
196
isFinalized() const197 OFBool DSRDocument::isFinalized() const
198 {
199 return FinalizedFlag;
200 }
201
202
print(STD_NAMESPACE ostream & stream,const size_t flags)203 OFCondition DSRDocument::print(STD_NAMESPACE ostream &stream,
204 const size_t flags)
205 {
206 OFCondition result = SR_EC_InvalidDocument;
207 if (isValid())
208 {
209 OFString tmpString, string2;
210 /* update only some DICOM attributes */
211 updateAttributes(OFFalse /*updateAll*/);
212 /* check whether general SR modules are used */
213 const OFBool usesGeneralSRModules = usesSRDocumentGeneralModule(getDocumentType());
214
215 // --- print some general document information ---
216
217 if (!(flags & PF_printNoDocumentHeader))
218 {
219 /* document type/title */
220 DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_DOCUMENT_TYPE)
221 stream << documentTypeToDocumentTitle(getDocumentType(), tmpString);
222 DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_RESET)
223 stream << OFendl << OFendl;
224 /* patient related information */
225 if (!PatientName.isEmpty())
226 {
227 DCMSR_PRINT_HEADER_FIELD_START("Patient ", " : ")
228 stream << getPrintStringFromElement(PatientName, tmpString);
229 OFString patientStr;
230 if (!PatientSex.isEmpty())
231 patientStr += getPrintStringFromElement(PatientSex, tmpString);
232 if (!PatientBirthDate.isEmpty())
233 {
234 if (!patientStr.empty())
235 patientStr += ", ";
236 patientStr += dicomToReadableDate(getStringValueFromElement(PatientBirthDate, tmpString), string2);
237 }
238 if (!PatientID.isEmpty())
239 {
240 if (!patientStr.empty())
241 patientStr += ", ";
242 patientStr += '#';
243 patientStr += getPrintStringFromElement(PatientID, tmpString);
244 if (!IssuerOfPatientID.isEmpty())
245 {
246 patientStr += ":";
247 patientStr += getPrintStringFromElement(IssuerOfPatientID, tmpString);
248 }
249 }
250 if (!patientStr.empty())
251 stream << " (" << patientStr << ")";
252 DCMSR_PRINT_HEADER_FIELD_END
253 }
254 /* referring physician */
255 if (!ReferringPhysicianName.isEmpty())
256 {
257 DCMSR_PRINT_HEADER_FIELD_START("Referring Physician", " : ")
258 stream << getPrintStringFromElement(ReferringPhysicianName, tmpString);
259 DCMSR_PRINT_HEADER_FIELD_END
260 }
261 /* study-related information */
262 if (!StudyDescription.isEmpty())
263 {
264 DCMSR_PRINT_HEADER_FIELD_START("Study ", " : ")
265 stream << getPrintStringFromElement(StudyDescription, tmpString);
266 if (!StudyID.isEmpty())
267 stream << " (#" << getPrintStringFromElement(StudyID, tmpString) << ")";
268 DCMSR_PRINT_HEADER_FIELD_END
269 }
270 /* series-related information */
271 if (!SeriesDescription.isEmpty())
272 {
273 DCMSR_PRINT_HEADER_FIELD_START("Series ", " : ")
274 stream << getPrintStringFromElement(SeriesDescription, tmpString);
275 if (!SeriesNumber.isEmpty())
276 stream << " (#" << getPrintStringFromElement(SeriesNumber, tmpString) << ")";
277 DCMSR_PRINT_HEADER_FIELD_END
278 }
279 /* protocol name */
280 if (!ProtocolName.isEmpty())
281 {
282 DCMSR_PRINT_HEADER_FIELD_START("Protocol ", " : ")
283 stream << getPrintStringFromElement(ProtocolName, tmpString);
284 DCMSR_PRINT_HEADER_FIELD_END
285 }
286 /* manufacturer and device */
287 if (!Manufacturer.isEmpty())
288 {
289 DCMSR_PRINT_HEADER_FIELD_START("Manufacturer ", " : ")
290 stream << getPrintStringFromElement(Manufacturer, tmpString);
291 OFString deviceStr;
292 if (!ManufacturerModelName.isEmpty())
293 deviceStr += getPrintStringFromElement(ManufacturerModelName, tmpString);
294 if (!DeviceSerialNumber.isEmpty())
295 {
296 if (!deviceStr.empty())
297 deviceStr += ", ";
298 deviceStr += '#';
299 deviceStr += getPrintStringFromElement(DeviceSerialNumber, tmpString);
300 }
301 if (!deviceStr.empty())
302 stream << " (" << deviceStr << ")";
303 DCMSR_PRINT_HEADER_FIELD_END
304 }
305 /* not all SR IODs contain the SR Document General Module */
306 if (usesGeneralSRModules)
307 {
308 /* preliminary flag */
309 if (!PreliminaryFlag.isEmpty())
310 {
311 DCMSR_PRINT_HEADER_FIELD_START("Preliminary Flag ", " : ")
312 stream << getStringValueFromElement(PreliminaryFlag, tmpString);
313 DCMSR_PRINT_HEADER_FIELD_END
314 }
315 /* completion flag */
316 DCMSR_PRINT_HEADER_FIELD_START("Completion Flag ", " : ")
317 stream << getStringValueFromElement(CompletionFlag, tmpString);
318 DCMSR_PRINT_HEADER_FIELD_END
319 if (!CompletionFlagDescription.isEmpty())
320 {
321 DCMSR_PRINT_HEADER_FIELD_START(" ", " ")
322 stream << getPrintStringFromElement(CompletionFlagDescription, tmpString);
323 DCMSR_PRINT_HEADER_FIELD_END
324 }
325 /* predecessor documents */
326 if (!PredecessorDocuments.isEmpty())
327 {
328 DCMSR_PRINT_HEADER_FIELD_START("Predecessor Docs ", " : ")
329 stream << PredecessorDocuments.getNumberOfInstances();
330 DCMSR_PRINT_HEADER_FIELD_END
331 }
332 }
333 /* identical documents */
334 if (!IdenticalDocuments.isEmpty())
335 {
336 DCMSR_PRINT_HEADER_FIELD_START("Identical Docs ", " : ")
337 stream << IdenticalDocuments.getNumberOfInstances();
338 DCMSR_PRINT_HEADER_FIELD_END
339 }
340 /* referenced instances */
341 if (!ReferencedInstances.isEmpty())
342 {
343 DCMSR_PRINT_HEADER_FIELD_START("References Objects ", " : ")
344 stream << ReferencedInstances.getNumberOfItems();
345 DCMSR_PRINT_HEADER_FIELD_END
346 }
347 if (usesGeneralSRModules)
348 {
349 /* verification flag */
350 DCMSR_PRINT_HEADER_FIELD_START("Verification Flag ", " : ")
351 stream << getStringValueFromElement(VerificationFlag, tmpString);
352 DCMSR_PRINT_HEADER_FIELD_END
353 /* verifying observer */
354 const size_t obsCount = getNumberOfVerifyingObservers();
355 for (size_t i = 1; i <= obsCount; i++)
356 {
357 OFString dateTime, obsName, organization;
358 DSRCodedEntryValue obsCode;
359 if (getVerifyingObserver(i, dateTime, obsName, obsCode, organization).good())
360 {
361 if (i == 1)
362 {
363 DCMSR_PRINT_HEADER_FIELD_START("Verifying Observers", " : ")
364 } else {
365 DCMSR_PRINT_HEADER_FIELD_START(" ", " ")
366 }
367 stream << dicomToReadableDateTime(dateTime, tmpString) << ", " << obsName;
368 if (obsCode.isValid() || (flags & PF_printInvalidCodes))
369 {
370 stream << " ";
371 obsCode.print(stream, (flags & PF_printAllCodes) > 0 /*printCodeValue*/, flags);
372 }
373 stream << ", " << organization;
374 DCMSR_PRINT_HEADER_FIELD_END
375 }
376 }
377 }
378 /* content date and time */
379 if (!ContentDate.isEmpty() && !ContentTime.isEmpty())
380 {
381 DCMSR_PRINT_HEADER_FIELD_START("Content Date/Time ", " : ")
382 stream << dicomToReadableDate(getStringValueFromElement(ContentDate, tmpString), string2) << " ";
383 stream << dicomToReadableTime(getStringValueFromElement(ContentTime, tmpString), string2);
384 DCMSR_PRINT_HEADER_FIELD_END
385 }
386 stream << OFendl;
387 }
388
389 // --- dump document tree to stream ---
390 result = DocumentTree.print(stream, flags);
391 }
392 return result;
393 }
394
395
checkDatasetForReading(DcmItem & dataset,E_DocumentType & documentType)396 OFCondition DSRDocument::checkDatasetForReading(DcmItem &dataset,
397 E_DocumentType &documentType)
398 {
399 OFCondition result = EC_Normal;
400 OFString tmpString;
401 DcmUniqueIdentifier sopClassUID(DCM_SOPClassUID);
402 DcmCodeString modality(DCM_Modality);
403 /* check SOP class UID */
404 result = getAndCheckElementFromDataset(dataset, sopClassUID, "1", "1", "SOPCommonModule");
405 if (result.good())
406 {
407 documentType = sopClassUIDToDocumentType(getStringValueFromElement(sopClassUID, tmpString));
408 DCMSR_DEBUG("Value of SOP Class UID: " << tmpString);
409 if (documentType == DT_invalid)
410 {
411 DCMSR_ERROR("SOP Class UID does not match one of the known SR document classes");
412 result = SR_EC_UnknownDocumentType;
413 }
414 else if (!isDocumentTypeSupported(documentType))
415 {
416 DCMSR_ERROR("Unsupported SOP Class UID (not yet implemented)");
417 result = SR_EC_UnsupportedValue;
418 }
419 } else {
420 /* no SOP Class UID means no document type */
421 documentType = DT_invalid;
422 }
423 /* check modality */
424 if (result.good())
425 {
426 if (usesKeyObjectDocumentSeriesModule(documentType))
427 result = getAndCheckElementFromDataset(dataset, modality, "1", "1", "KeyObjectDocumentSeriesModule");
428 else
429 result = getAndCheckElementFromDataset(dataset, modality, "1", "1", "SRDocumentSeriesModule");
430 if (result.good())
431 {
432 if (getStringValueFromElement(modality, tmpString) != documentTypeToModality(documentType))
433 {
434 DCMSR_ERROR("Modality does not match '" << documentTypeToModality(documentType) << "' for "
435 << documentTypeToReadableName(documentType));
436 }
437 }
438 }
439 return result;
440 }
441
442
read(DcmItem & dataset,const size_t flags)443 OFCondition DSRDocument::read(DcmItem &dataset,
444 const size_t flags)
445 {
446 OFCondition result = EC_Normal;
447 E_DocumentType documentType = DT_invalid;
448 DCMSR_DEBUG("Reading SR document from DICOM dataset");
449 /* re-initialize SR document */
450 clear();
451 /* check SOP class UID and modality first */
452 result = checkDatasetForReading(dataset, documentType);
453 /* dataset is OK */
454 if (result.good())
455 {
456 OFString tmpString;
457 OFCondition searchCond = EC_Normal;
458 OFCondition obsSearchCond = EC_Normal;
459
460 /* type 3 element and attributes which have already been checked are not checked */
461
462 // --- SOP Common Module ---
463 getElementFromDataset(dataset, SOPClassUID); /* already checked */
464 getAndCheckElementFromDataset(dataset, SOPInstanceUID, "1", "1", "SOPCommonModule");
465 getAndCheckElementFromDataset(dataset, SpecificCharacterSet, "1-n", "1C", "SOPCommonModule");
466 getStringValueFromElement(SpecificCharacterSet, tmpString, -1 /* all components */);
467 /* currently, the VR checker in 'dcmdata' only supports ASCII and Latin-1 */
468 if (!tmpString.empty() && (tmpString != "ISO_IR 6") && (tmpString != "ISO_IR 100"))
469 DCMSR_WARN("The VR checker does not support this Specific Character Set: " << tmpString);
470 getAndCheckElementFromDataset(dataset, InstanceCreationDate, "1", "3", "SOPCommonModule");
471 getAndCheckElementFromDataset(dataset, InstanceCreationTime, "1", "3", "SOPCommonModule");
472 getAndCheckElementFromDataset(dataset, InstanceCreatorUID, "1", "3", "SOPCommonModule");
473 CodingSchemeIdentification.read(dataset, flags);
474 if (requiresTimezoneModule(documentType))
475 {
476 // --- Timezone Module ---
477 getAndCheckElementFromDataset(dataset, TimezoneOffsetFromUTC, "1", "1", "TimezoneModule");
478 } else {
479 // --- SOP Common Module ---
480 getAndCheckElementFromDataset(dataset, TimezoneOffsetFromUTC, "1", "3", "SOPCommonModule");
481 }
482
483 // --- General Study and Patient Module ---
484 readStudyData(dataset, flags);
485
486 if (requiresEnhancedEquipmentModule(documentType))
487 {
488 // --- Enhanced General Equipment Module ---
489 getAndCheckElementFromDataset(dataset, Manufacturer, "1", "1", "EnhancedGeneralEquipmentModule");
490 getAndCheckElementFromDataset(dataset, ManufacturerModelName, "1", "1", "EnhancedGeneralEquipmentModule");
491 getAndCheckElementFromDataset(dataset, DeviceSerialNumber, "1", "1", "EnhancedGeneralEquipmentModule");
492 getAndCheckElementFromDataset(dataset, SoftwareVersions, "1-n", "1", "EnhancedGeneralEquipmentModule");
493 } else {
494 // --- General Equipment Module ---
495 getAndCheckElementFromDataset(dataset, Manufacturer, "1", "2", "GeneralEquipmentModule");
496 getAndCheckElementFromDataset(dataset, ManufacturerModelName, "1", "3", "GeneralEquipmentModule");
497 getAndCheckElementFromDataset(dataset, DeviceSerialNumber, "1", "3", "GeneralEquipmentModule");
498 getAndCheckElementFromDataset(dataset, SoftwareVersions, "1-n", "3", "GeneralEquipmentModule");
499 }
500
501 // --- Synchronization Module ---
502 if (requiresSynchronizationModule(documentType) /* either the IOD requires this module */ ||
503 dataset.tagExistsWithValue(DCM_SynchronizationFrameOfReferenceUID) || dataset.tagExistsWithValue(DCM_SynchronizationTrigger) ||
504 dataset.tagExistsWithValue(DCM_AcquisitionTimeSynchronized) /* or all attributes should be absent */ )
505 {
506 getAndCheckElementFromDataset(dataset, SynchronizationFrameOfReferenceUID, "1", "1", "SynchronizationModule");
507 getAndCheckElementFromDataset(dataset, SynchronizationTrigger, "1", "1", "SynchronizationModule");
508 getAndCheckElementFromDataset(dataset, AcquisitionTimeSynchronized, "1", "1", "SynchronizationModule");
509 }
510
511 // --- SR Document Series Module / Key Object Document Series Module ---
512 getElementFromDataset(dataset, Modality); /* already checked */
513 if (usesKeyObjectDocumentSeriesModule(documentType))
514 {
515 getAndCheckElementFromDataset(dataset, SeriesInstanceUID, "1", "1", "KeyObjectDocumentSeriesModule");
516 getAndCheckElementFromDataset(dataset, SeriesNumber, "1", "1", "KeyObjectDocumentSeriesModule");
517 getAndCheckElementFromDataset(dataset, SeriesDate, "1", "3", "KeyObjectDocumentSeriesModule");
518 getAndCheckElementFromDataset(dataset, SeriesTime, "1", "3", "KeyObjectDocumentSeriesModule");
519 getAndCheckElementFromDataset(dataset, ProtocolName, "1", "3", "KeyObjectDocumentSeriesModule");
520 getAndCheckElementFromDataset(dataset, SeriesDescription, "1", "3", "KeyObjectDocumentSeriesModule");
521 /* need to check sequence in two steps (avoids additional getAndCheck... method) */
522 searchCond = getElementFromDataset(dataset, ReferencedPerformedProcedureStep);
523 checkElementValue(ReferencedPerformedProcedureStep, "1", "2", searchCond, "KeyObjectDocumentSeriesModule");
524 } else {
525 getAndCheckElementFromDataset(dataset, SeriesInstanceUID, "1", "1", "SRDocumentSeriesModule");
526 getAndCheckElementFromDataset(dataset, SeriesNumber, "1", "1", "SRDocumentSeriesModule");
527 getAndCheckElementFromDataset(dataset, SeriesDate, "1", "3", "SRDocumentSeriesModule");
528 getAndCheckElementFromDataset(dataset, SeriesTime, "1", "3", "SRDocumentSeriesModule");
529 getAndCheckElementFromDataset(dataset, ProtocolName, "1", "3", "SRDocumentSeriesModule");
530 getAndCheckElementFromDataset(dataset, SeriesDescription, "1", "3", "SRDocumentSeriesModule");
531 /* need to check sequence in two steps (avoids additional getAndCheck... method) */
532 searchCond = getElementFromDataset(dataset, ReferencedPerformedProcedureStep);
533 checkElementValue(ReferencedPerformedProcedureStep, "1", "2", searchCond, "SRDocumentSeriesModule");
534 }
535 /* remove possible signature sequences */
536 removeAttributeFromSequence(ReferencedPerformedProcedureStep, DCM_MACParametersSequence);
537 removeAttributeFromSequence(ReferencedPerformedProcedureStep, DCM_DigitalSignaturesSequence);
538
539 // --- SR Document General Module / Key Object Document Module ---
540 if (usesKeyObjectDocumentModule(documentType))
541 {
542 getAndCheckElementFromDataset(dataset, InstanceNumber, "1", "1", "KeyObjectDocumentModule");
543 getAndCheckElementFromDataset(dataset, ContentDate, "1", "1", "KeyObjectDocumentModule");
544 getAndCheckElementFromDataset(dataset, ContentTime, "1", "1", "KeyObjectDocumentModule");
545 } else {
546 getAndCheckElementFromDataset(dataset, InstanceNumber, "1", "1", "SRDocumentGeneralModule");
547 getAndCheckElementFromDataset(dataset, ContentDate, "1", "1", "SRDocumentGeneralModule");
548 getAndCheckElementFromDataset(dataset, ContentTime, "1", "1", "SRDocumentGeneralModule");
549 getAndCheckElementFromDataset(dataset, PreliminaryFlag, "1", "3", "SRDocumentGeneralModule");
550 getAndCheckElementFromDataset(dataset, CompletionFlag, "1", "1", "SRDocumentGeneralModule");
551 getAndCheckElementFromDataset(dataset, CompletionFlagDescription, "1", "3", "SRDocumentGeneralModule");
552 getAndCheckElementFromDataset(dataset, VerificationFlag, "1", "1", "SRDocumentGeneralModule");
553 obsSearchCond = getElementFromDataset(dataset, VerifyingObserver);
554 PredecessorDocuments.read(dataset, flags);
555 /* need to check sequence in two steps (avoids additional getAndCheck... method) */
556 searchCond = getElementFromDataset(dataset, PerformedProcedureCode);
557 checkElementValue(PerformedProcedureCode, "1-n", "2", searchCond, "SRDocumentGeneralModule");
558 PertinentOtherEvidence.read(dataset, flags);
559 ReferencedInstances.read(dataset, flags);
560 }
561 IdenticalDocuments.read(dataset, flags);
562 CurrentRequestedProcedureEvidence.read(dataset, flags);
563 /* remove possible signature sequences */
564 removeAttributeFromSequence(VerifyingObserver, DCM_MACParametersSequence);
565 removeAttributeFromSequence(VerifyingObserver, DCM_DigitalSignaturesSequence);
566 removeAttributeFromSequence(PerformedProcedureCode, DCM_MACParametersSequence);
567 removeAttributeFromSequence(PerformedProcedureCode, DCM_DigitalSignaturesSequence);
568
569 /* update internal enumerated values and perform additional checks */
570
571 /* not all SR IODs contain the SR Document General Module */
572 if (usesSRDocumentGeneralModule(documentType))
573 {
574 /* get and check PreliminaryFlag (if present) */
575 if (!PreliminaryFlag.isEmpty())
576 {
577 PreliminaryFlagEnum = enumeratedValueToPreliminaryFlag(getStringValueFromElement(PreliminaryFlag, tmpString));
578 if (PreliminaryFlagEnum == PF_invalid)
579 printUnknownValueWarningMessage("PreliminaryFlag", tmpString.c_str());
580 }
581 /* get and check CompletionFlag */
582 CompletionFlagEnum = enumeratedValueToCompletionFlag(getStringValueFromElement(CompletionFlag, tmpString));
583 if (CompletionFlagEnum == CF_invalid)
584 printUnknownValueWarningMessage("CompletionFlag", tmpString.c_str());
585 else if ((documentType == DT_XRayRadiationDoseSR) && (CompletionFlagEnum != CF_Complete))
586 DCMSR_WARN("Invalid value for Completion Flag, should be 'COMPLETE' for X-Ray Radiation Dose SR");
587 /* get and check VerificationFlag / VerifyingObserverSequence */
588 VerificationFlagEnum = enumeratedValueToVerificationFlag(getStringValueFromElement(VerificationFlag, tmpString));
589 if (VerificationFlagEnum == VF_invalid)
590 printUnknownValueWarningMessage("VerificationFlag", tmpString.c_str());
591 else if (VerificationFlagEnum == VF_Verified)
592 checkElementValue(VerifyingObserver, "1-n", "1", obsSearchCond, "SRDocumentGeneralModule");
593 }
594 getStringValueFromElement(SpecificCharacterSet, tmpString, -1 /* all components */);
595 SpecificCharacterSetEnum = definedTermToCharacterSet(tmpString);
596 /* check SpecificCharacterSet */
597 if ((SpecificCharacterSetEnum == CS_invalid) && !tmpString.empty())
598 printUnknownValueWarningMessage("SpecificCharacterSet", tmpString.c_str());
599
600 /* read SR document tree */
601 if (result.good())
602 result = DocumentTree.read(dataset, documentType, flags);
603 }
604 return result;
605 }
606
607
readPatientData(DcmItem & dataset,const size_t)608 OFCondition DSRDocument::readPatientData(DcmItem &dataset,
609 const size_t /*flags*/)
610 {
611 // --- Patient Module ---
612 getAndCheckElementFromDataset(dataset, PatientName, "1", "2", "PatientModule");
613 getAndCheckElementFromDataset(dataset, PatientID, "1", "2", "PatientModule");
614 getAndCheckElementFromDataset(dataset, IssuerOfPatientID, "1", "3", "PatientModule");
615 getAndCheckElementFromDataset(dataset, PatientBirthDate, "1", "2", "PatientModule");
616 getAndCheckElementFromDataset(dataset, PatientSex, "1", "2", "PatientModule");
617 /* always return success */
618 return EC_Normal;
619 }
620
621
readStudyData(DcmItem & dataset,const size_t flags)622 OFCondition DSRDocument::readStudyData(DcmItem &dataset,
623 const size_t flags)
624 {
625 // --- General Study Module ---
626 getAndCheckElementFromDataset(dataset, StudyInstanceUID, "1", "1", "GeneralStudyModule");
627 getAndCheckElementFromDataset(dataset, StudyDate, "1", "2", "GeneralStudyModule");
628 getAndCheckElementFromDataset(dataset, StudyTime, "1", "2", "GeneralStudyModule");
629 getAndCheckElementFromDataset(dataset, ReferringPhysicianName, "1", "2", "GeneralStudyModule");
630 getAndCheckElementFromDataset(dataset, StudyID, "1", "2", "GeneralStudyModule");
631 getAndCheckElementFromDataset(dataset, AccessionNumber, "1", "2", "GeneralStudyModule");
632 getAndCheckElementFromDataset(dataset, StudyDescription, "1", "3", "GeneralStudyModule");
633 /* also read data from Patient Module */
634 return readPatientData(dataset, flags);
635 }
636
637
write(DcmItem & dataset,DcmStack * markedItems)638 OFCondition DSRDocument::write(DcmItem &dataset,
639 DcmStack *markedItems)
640 {
641 OFCondition result = EC_Normal;
642 /* only write valid documents */
643 if (isValid())
644 {
645 DCMSR_DEBUG("Writing SR document to DICOM dataset");
646 /* update all DICOM attributes */
647 updateAttributes();
648
649 /* checking particular values */
650 if ((getDocumentType() == DT_XRayRadiationDoseSR) && (CompletionFlagEnum != CF_Complete))
651 DCMSR_WARN("Invalid value for Completion Flag, should be 'COMPLETE' for X-Ray Radiation Dose SR");
652
653 /* write general document attributes */
654
655 // --- SOP Common Module ---
656 addElementToDataset(result, dataset, new DcmUniqueIdentifier(SOPClassUID), "1", "1", "SOPCommonModule");
657 addElementToDataset(result, dataset, new DcmUniqueIdentifier(SOPInstanceUID), "1", "1", "SOPCommonModule");
658 /* never write specific character set for ASCII (default character repertoire) */
659 if (SpecificCharacterSetEnum != CS_ASCII)
660 addElementToDataset(result, dataset, new DcmCodeString(SpecificCharacterSet), "1-n", "1C", "SOPCommonModule");
661 addElementToDataset(result, dataset, new DcmDate(InstanceCreationDate), "1", "3", "SOPCommonModule");
662 addElementToDataset(result, dataset, new DcmTime(InstanceCreationTime), "1", "3", "SOPCommonModule");
663 addElementToDataset(result, dataset, new DcmUniqueIdentifier(InstanceCreatorUID), "1", "3", "SOPCommonModule");
664 CodingSchemeIdentification.write(dataset);
665 if (requiresTimezoneModule(getDocumentType()))
666 {
667 // --- Timezone Module ---
668 addElementToDataset(result, dataset, new DcmShortString(TimezoneOffsetFromUTC), "1", "1", "TimezoneModule");
669 } else {
670 // --- SOP Common Module ---
671 addElementToDataset(result, dataset, new DcmShortString(TimezoneOffsetFromUTC), "1", "3", "SOPCommonModule");
672 }
673
674 // --- General Study Module ---
675 addElementToDataset(result, dataset, new DcmUniqueIdentifier(StudyInstanceUID), "1", "1", "GeneralStudyModule");
676 addElementToDataset(result, dataset, new DcmDate(StudyDate), "1", "2", "GeneralStudyModule");
677 addElementToDataset(result, dataset, new DcmTime(StudyTime), "1", "2", "GeneralStudyModule");
678 addElementToDataset(result, dataset, new DcmPersonName(ReferringPhysicianName), "1", "2", "GeneralStudyModule");
679 addElementToDataset(result, dataset, new DcmShortString(StudyID), "1", "2", "GeneralStudyModule");
680 addElementToDataset(result, dataset, new DcmShortString(AccessionNumber), "1", "2", "GeneralStudyModule");
681 addElementToDataset(result, dataset, new DcmLongString(StudyDescription), "1", "3", "GeneralStudyModule");
682
683 // --- Patient Module ---
684 addElementToDataset(result, dataset, new DcmPersonName(PatientName), "1", "2", "PatientModule");
685 addElementToDataset(result, dataset, new DcmLongString(PatientID), "1", "2", "PatientModule");
686 addElementToDataset(result, dataset, new DcmLongString(IssuerOfPatientID), "1", "3", "PatientModule");
687 addElementToDataset(result, dataset, new DcmDate(PatientBirthDate), "1", "2", "PatientModule");
688 addElementToDataset(result, dataset, new DcmCodeString(PatientSex), "1", "2", "PatientModule");
689
690 if (requiresEnhancedEquipmentModule(getDocumentType()))
691 {
692 // --- Enhanced General Equipment Module ---
693 addElementToDataset(result, dataset, new DcmLongString(Manufacturer), "1", "1", "EnhancedGeneralEquipmentModule");
694 addElementToDataset(result, dataset, new DcmLongString(ManufacturerModelName), "1", "1", "EnhancedGeneralEquipmentModule");
695 addElementToDataset(result, dataset, new DcmLongString(DeviceSerialNumber), "1", "1", "EnhancedGeneralEquipmentModule");
696 addElementToDataset(result, dataset, new DcmLongString(SoftwareVersions), "1-n", "1", "EnhancedGeneralEquipmentModule");
697 } else {
698 // --- General Equipment Module ---
699 addElementToDataset(result, dataset, new DcmLongString(Manufacturer), "1", "2", "GeneralEquipmentModule");
700 addElementToDataset(result, dataset, new DcmLongString(ManufacturerModelName), "1", "3", "GeneralEquipmentModule");
701 addElementToDataset(result, dataset, new DcmLongString(DeviceSerialNumber), "1", "3", "GeneralEquipmentModule");
702 addElementToDataset(result, dataset, new DcmLongString(SoftwareVersions), "1-n", "3", "GeneralEquipmentModule");
703 }
704
705 // --- Synchronization Module ---
706 if (requiresSynchronizationModule(getDocumentType()) /* module required for some IODs */ ||
707 !SynchronizationFrameOfReferenceUID.isEmpty() || !SynchronizationTrigger.isEmpty() || !AcquisitionTimeSynchronized.isEmpty())
708 {
709 addElementToDataset(result, dataset, new DcmUniqueIdentifier(SynchronizationFrameOfReferenceUID), "1", "1", "SynchronizationModule");
710 addElementToDataset(result, dataset, new DcmCodeString(SynchronizationTrigger), "1", "1", "SynchronizationModule");
711 addElementToDataset(result, dataset, new DcmCodeString(AcquisitionTimeSynchronized), "1", "1", "SynchronizationModule");
712 }
713
714 // --- SR Document Series Module / Key Object Document Series Module ---
715 if (usesKeyObjectDocumentSeriesModule(getDocumentType()))
716 {
717 addElementToDataset(result, dataset, new DcmCodeString(Modality), "1", "1", "KeyObjectDocumentSeriesModule");
718 addElementToDataset(result, dataset, new DcmUniqueIdentifier(SeriesInstanceUID), "1", "1", "KeyObjectDocumentSeriesModule");
719 addElementToDataset(result, dataset, new DcmIntegerString(SeriesNumber), "1", "1", "KeyObjectDocumentSeriesModule");
720 addElementToDataset(result, dataset, new DcmDate(SeriesDate), "1", "3", "KeyObjectDocumentSeriesModule");
721 addElementToDataset(result, dataset, new DcmTime(SeriesTime), "1", "3", "KeyObjectDocumentSeriesModule");
722 addElementToDataset(result, dataset, new DcmLongString(ProtocolName), "1", "3", "KeyObjectDocumentSeriesModule");
723 addElementToDataset(result, dataset, new DcmLongString(SeriesDescription), "1", "3", "KeyObjectDocumentSeriesModule");
724 /* always write empty sequence since not yet fully supported */
725 ReferencedPerformedProcedureStep.clear();
726 addElementToDataset(result, dataset, new DcmSequenceOfItems(ReferencedPerformedProcedureStep), "1", "2", "KeyObjectDocumentSeriesModule");
727 } else {
728 addElementToDataset(result, dataset, new DcmCodeString(Modality), "1", "1", "SRDocumentSeriesModule");
729 addElementToDataset(result, dataset, new DcmUniqueIdentifier(SeriesInstanceUID), "1", "1", "SRDocumentSeriesModule");
730 addElementToDataset(result, dataset, new DcmIntegerString(SeriesNumber), "1", "1", "SRDocumentSeriesModule");
731 addElementToDataset(result, dataset, new DcmDate(SeriesDate), "1", "3", "SRDocumentSeriesModule");
732 addElementToDataset(result, dataset, new DcmTime(SeriesTime), "1", "3", "SRDocumentSeriesModule");
733 addElementToDataset(result, dataset, new DcmLongString(ProtocolName), "1", "3", "SRDocumentSeriesModule");
734 addElementToDataset(result, dataset, new DcmLongString(SeriesDescription), "1", "3", "SRDocumentSeriesModule");
735 /* always write empty sequence since not yet fully supported */
736 ReferencedPerformedProcedureStep.clear();
737 addElementToDataset(result, dataset, new DcmSequenceOfItems(ReferencedPerformedProcedureStep), "1", "2", "SRDocumentSeriesModule");
738 }
739
740 // --- SR Document General Module / Key Object Document Module ---
741 if (usesKeyObjectDocumentModule(getDocumentType()))
742 {
743 addElementToDataset(result, dataset, new DcmIntegerString(InstanceNumber), "1", "1", "KeyObjectDocumentModule");
744 addElementToDataset(result, dataset, new DcmDate(ContentDate), "1", "1", "KeyObjectDocumentModule");
745 addElementToDataset(result, dataset, new DcmTime(ContentTime), "1", "1", "KeyObjectDocumentModule");
746 } else {
747 addElementToDataset(result, dataset, new DcmIntegerString(InstanceNumber), "1", "1", "SRDocumentGeneralModule");
748 addElementToDataset(result, dataset, new DcmDate(ContentDate), "1", "1", "SRDocumentGeneralModule");
749 addElementToDataset(result, dataset, new DcmTime(ContentTime), "1", "1", "SRDocumentGeneralModule");
750 addElementToDataset(result, dataset, new DcmCodeString(PreliminaryFlag), "1", "3", "SRDocumentGeneralModule");
751 addElementToDataset(result, dataset, new DcmCodeString(CompletionFlag), "1", "1", "SRDocumentGeneralModule");
752 addElementToDataset(result, dataset, new DcmLongString(CompletionFlagDescription), "1", "3", "SRDocumentGeneralModule");
753 addElementToDataset(result, dataset, new DcmCodeString(VerificationFlag), "1", "1", "SRDocumentGeneralModule");
754 if (VerificationFlagEnum == VF_Verified)
755 addElementToDataset(result, dataset, new DcmSequenceOfItems(VerifyingObserver), "1-n", "1", "SRDocumentGeneralModule");
756 if (result.good())
757 PredecessorDocuments.write(dataset); /* optional */
758 /* always write empty sequence since not yet fully supported */
759 PerformedProcedureCode.clear();
760 addElementToDataset(result, dataset, new DcmSequenceOfItems(PerformedProcedureCode), "1-n", "2", "SRDocumentGeneralModule");
761 if (result.good())
762 result = PertinentOtherEvidence.write(dataset);
763 if (result.good())
764 result = ReferencedInstances.write(dataset);
765 }
766
767 if (result.good())
768 IdenticalDocuments.write(dataset); /* optional */
769 if (result.good())
770 result = CurrentRequestedProcedureEvidence.write(dataset);
771
772 /* write SR document tree */
773 if (result.good())
774 result = DocumentTree.write(dataset, markedItems);
775 } else
776 result = SR_EC_InvalidDocument;
777 return result;
778 }
779
780
readXML(const OFString & filename,const size_t flags)781 OFCondition DSRDocument::readXML(const OFString &filename,
782 const size_t flags)
783 {
784 DSRXMLDocument doc;
785 DCMSR_DEBUG("Reading SR document from XML format");
786 /* read, parse and validate XML document */
787 OFCondition result = doc.read(filename, flags);
788 if (result.good())
789 {
790 /* re-initialize SR document */
791 clear();
792 /* start with document root node */
793 DSRXMLCursor cursor(doc.getRootNode());
794 /* check whether we really parse a "report" document */
795 result = doc.checkNode(cursor, "report");
796 if (result.good())
797 {
798 /* goto sub-element "sopclass" (first child node!) */
799 result = doc.checkNode(cursor.gotoChild(), "sopclass");
800 if (result.good())
801 {
802 /* determine document type (SOP class) */
803 result = doc.getElementFromAttribute(cursor, SOPClassUID, "uid");
804 if (result.good())
805 {
806 OFString sopClassUID;
807 getSOPClassUID(sopClassUID);
808 /* create new document of specified type (also checks for support) */
809 result = createNewDocument(sopClassUIDToDocumentType(sopClassUID));
810 if (result.good())
811 {
812 /* proceed with document header */
813 result = readXMLDocumentHeader(doc, cursor.gotoNext(), flags);
814 } else
815 DCMSR_ERROR("Unknown/Unsupported SOP Class UID");
816 }
817 }
818 }
819 }
820 return result;
821 }
822
823
readXMLDocumentHeader(DSRXMLDocument & doc,DSRXMLCursor cursor,const size_t flags)824 OFCondition DSRDocument::readXMLDocumentHeader(DSRXMLDocument &doc,
825 DSRXMLCursor cursor,
826 const size_t flags)
827 {
828 OFCondition result = SR_EC_InvalidDocument;
829 if (doc.valid() && cursor.valid())
830 {
831 result = EC_Normal;
832 /* iterate over all nodes */
833 while (cursor.valid() && result.good())
834 {
835 /* check for known element tags */
836 if (doc.matchNode(cursor, "charset"))
837 {
838 /* use "charset" to decode special characters (has to be at the beginning) */
839 if (!doc.encodingHandlerValid())
840 {
841 OFString tmpString;
842 /* check for known character set */
843 setSpecificCharacterSet(doc.getStringFromNodeContent(cursor, tmpString));
844 if (tmpString.empty())
845 DCMSR_WARN("Empty value for 'charset' ... ignoring");
846 else {
847 const char *encString = characterSetToXMLName(SpecificCharacterSetEnum);
848 if ((strcmp(encString, "?") == 0) || doc.setEncodingHandler(encString).bad())
849 DCMSR_WARN("Character set '" << tmpString << "' not supported");
850 }
851 } else {
852 /* only one "charset" node allowed */
853 doc.printUnexpectedNodeWarning(cursor);
854 }
855 }
856 else if (doc.matchNode(cursor, "timezone"))
857 {
858 doc.getElementFromNodeContent(cursor, TimezoneOffsetFromUTC, NULL, OFTrue /*encoding*/);
859 }
860 else if (doc.matchNode(cursor, "modality"))
861 {
862 OFString tmpString;
863 /* compare the XML node content */
864 if (doc.getStringFromNodeContent(cursor, tmpString) != documentTypeToModality(getDocumentType()))
865 DCMSR_WARN("Invalid value for 'modality' ... ignoring");
866 }
867 else if (doc.matchNode(cursor, "device"))
868 {
869 doc.getElementFromNodeContent(doc.getNamedChildNode(cursor, "manufacturer", OFFalse /*required*/), Manufacturer, NULL, OFTrue /*encoding*/);
870 doc.getElementFromNodeContent(doc.getNamedChildNode(cursor, "model"), ManufacturerModelName, NULL, OFTrue /*encoding*/);
871 doc.getElementFromNodeContent(doc.getNamedChildNode(cursor, "serial", OFFalse /*required*/), DeviceSerialNumber, NULL, OFTrue /*encoding*/);
872 doc.getElementFromNodeContent(doc.getNamedChildNode(cursor, "version", OFFalse /*required*/), SoftwareVersions, NULL, OFTrue /*encoding*/);
873 }
874 else if (doc.matchNode(cursor, "manufacturer"))
875 doc.getElementFromNodeContent(cursor, Manufacturer, "manufacturer", OFTrue /*encoding*/);
876 else if (doc.matchNode(cursor, "synchronization"))
877 {
878 doc.getElementFromAttribute(cursor, SynchronizationFrameOfReferenceUID, "uid");
879 doc.getElementFromNodeContent(doc.getNamedChildNode(cursor, "trigger"), SynchronizationTrigger);
880 doc.getElementFromNodeContent(doc.getNamedChildNode(cursor, "acquisitiontime"), AcquisitionTimeSynchronized);
881 }
882 else if (doc.matchNode(cursor, "referringphysician"))
883 {
884 /* goto sub-element "name" */
885 const DSRXMLCursor childNode = doc.getNamedChildNode(cursor, "name");
886 if (childNode.valid())
887 {
888 /* Referring Physician's Name */
889 OFString tmpString;
890 DSRPNameTreeNode::getValueFromXMLNodeContent(doc, childNode.getChild(), tmpString);
891 ReferringPhysicianName.putOFStringArray(tmpString);
892 }
893 }
894 else if (doc.matchNode(cursor, "patient"))
895 result = readXMLPatientData(doc, cursor.getChild(), flags);
896 else if (doc.matchNode(cursor, "study"))
897 result = readXMLStudyData(doc, cursor, flags);
898 else if (doc.matchNode(cursor, "series"))
899 result = readXMLSeriesData(doc, cursor, flags);
900 else if (doc.matchNode(cursor, "instance"))
901 result = readXMLInstanceData(doc, cursor, flags);
902 else if (doc.matchNode(cursor, "coding"))
903 {
904 const DSRXMLCursor childNode = cursor.getChild();
905 if (childNode.valid())
906 result = CodingSchemeIdentification.readXML(doc, childNode, flags);
907 }
908 else if (doc.matchNode(cursor, "evidence"))
909 {
910 OFString typeString;
911 /* check "type" attribute for corresponding sequence */
912 if (doc.getStringFromAttribute(cursor, typeString, "type") == "Current Requested Procedure")
913 result = CurrentRequestedProcedureEvidence.readXML(doc, cursor.getChild(), flags);
914 else if (typeString == "Pertinent Other")
915 {
916 if (usesSRDocumentGeneralModule(getDocumentType()))
917 result = PertinentOtherEvidence.readXML(doc, cursor.getChild(), flags);
918 else
919 doc.printUnexpectedNodeWarning(cursor);
920 } else // none of the standard defined evidence types
921 printUnknownValueWarningMessage("Evidence type", typeString.c_str());
922 }
923 else if (doc.matchNode(cursor, "reference"))
924 {
925 const DSRXMLCursor childNode = cursor.getChild();
926 if (childNode.valid())
927 result = ReferencedInstances.readXML(doc, childNode, flags);
928 }
929 else if (doc.matchNode(cursor, "document"))
930 result = readXMLDocumentData(doc, cursor.getChild(), flags);
931 else
932 doc.printUnexpectedNodeWarning(cursor);
933 /* print node error message (if any) */
934 doc.printGeneralNodeError(cursor, result);
935 /* proceed with next node */
936 cursor.gotoNext();
937 }
938 }
939 return result;
940 }
941
942
readXMLPatientData(const DSRXMLDocument & doc,DSRXMLCursor cursor,const size_t)943 OFCondition DSRDocument::readXMLPatientData(const DSRXMLDocument &doc,
944 DSRXMLCursor cursor,
945 const size_t /*flags*/)
946 {
947 OFCondition result = SR_EC_InvalidDocument;
948 if (cursor.valid())
949 {
950 OFString tmpString;
951 result = EC_Normal;
952 /* iterate over all nodes */
953 while (cursor.valid())
954 {
955 /* check for known element tags (all type 2) */
956 if (doc.matchNode(cursor, "name"))
957 {
958 /* Patient's Name */
959 DSRPNameTreeNode::getValueFromXMLNodeContent(doc, cursor.getChild(), tmpString);
960 PatientName.putOFStringArray(tmpString);
961 }
962 else if (doc.matchNode(cursor, "birthday"))
963 {
964 /* Patient's Birth Date */
965 DSRDateTreeNode::getValueFromXMLNodeContent(doc, doc.getNamedChildNode(cursor, "date"), tmpString);
966 PatientBirthDate.putOFStringArray(tmpString);
967 }
968 else if (doc.getElementFromNodeContent(cursor, PatientID, "id").bad() &&
969 doc.getElementFromNodeContent(cursor, IssuerOfPatientID, "issuer").bad() &&
970 doc.getElementFromNodeContent(cursor, PatientSex, "sex").bad())
971 {
972 doc.printUnexpectedNodeWarning(cursor);
973 }
974 /* proceed with next node */
975 cursor.gotoNext();
976 }
977 }
978 return result;
979 }
980
981
readXMLStudyData(const DSRXMLDocument & doc,DSRXMLCursor cursor,const size_t flags)982 OFCondition DSRDocument::readXMLStudyData(const DSRXMLDocument &doc,
983 DSRXMLCursor cursor,
984 const size_t flags)
985 {
986 OFCondition result = SR_EC_InvalidDocument;
987 if (cursor.valid())
988 {
989 OFString tmpString;
990 /* get Study Instance UID from XML attribute */
991 if (flags & XF_acceptEmptyStudySeriesInstanceUID)
992 {
993 if (doc.getElementFromAttribute(cursor, StudyInstanceUID, "uid", OFFalse /*encoding*/, OFFalse /*required*/).bad())
994 doc.printMissingAttributeWarning(cursor, "uid");
995 result = EC_Normal;
996 } else
997 result = doc.getElementFromAttribute(cursor, StudyInstanceUID, "uid");
998 /* goto first sub-element */
999 cursor.gotoChild();
1000 /* iterate over all nodes */
1001 while (cursor.valid())
1002 {
1003 /* check for known element tags */
1004 if (doc.matchNode(cursor, "accession"))
1005 {
1006 /* goto sub-element "number" */
1007 doc.getElementFromNodeContent(doc.getNamedChildNode(cursor, "number"), AccessionNumber);
1008 }
1009 else if (doc.matchNode(cursor, "date"))
1010 {
1011 DSRDateTreeNode::getValueFromXMLNodeContent(doc, cursor, tmpString);
1012 StudyDate.putOFStringArray(tmpString);
1013 }
1014 else if (doc.matchNode(cursor, "time"))
1015 {
1016 DSRTimeTreeNode::getValueFromXMLNodeContent(doc, cursor, tmpString);
1017 StudyTime.putOFStringArray(tmpString);
1018 }
1019 else if (doc.getElementFromNodeContent(cursor, StudyID, "id").bad() &&
1020 doc.getElementFromNodeContent(cursor, StudyDescription, "description", OFTrue /*encoding*/).bad())
1021 {
1022 doc.printUnexpectedNodeWarning(cursor);
1023 }
1024 /* proceed with next node */
1025 cursor.gotoNext();
1026 }
1027 /* check required element values */
1028 checkElementValue(StudyInstanceUID, "1", "1");
1029 }
1030 return result;
1031 }
1032
1033
readXMLSeriesData(const DSRXMLDocument & doc,DSRXMLCursor cursor,const size_t flags)1034 OFCondition DSRDocument::readXMLSeriesData(const DSRXMLDocument &doc,
1035 DSRXMLCursor cursor,
1036 const size_t flags)
1037 {
1038 OFCondition result = SR_EC_InvalidDocument;
1039 if (cursor.valid())
1040 {
1041 OFString tmpString;
1042 /* get Series Instance UID from XML attribute */
1043 if (flags & XF_acceptEmptyStudySeriesInstanceUID)
1044 {
1045 if (doc.getElementFromAttribute(cursor, SeriesInstanceUID, "uid", OFFalse /*encoding*/, OFFalse /*required*/).bad())
1046 doc.printMissingAttributeWarning(cursor, "uid");
1047 result = EC_Normal;
1048 } else
1049 result = doc.getElementFromAttribute(cursor, SeriesInstanceUID, "uid");
1050 /* goto first sub-element */
1051 cursor.gotoChild();
1052 /* iterate over all nodes */
1053 while (cursor.valid())
1054 {
1055 /* check for known element tags */
1056 if (doc.matchNode(cursor, "date"))
1057 {
1058 DSRDateTreeNode::getValueFromXMLNodeContent(doc, cursor, tmpString);
1059 SeriesDate.putOFStringArray(tmpString);
1060 }
1061 else if (doc.matchNode(cursor, "time"))
1062 {
1063 DSRTimeTreeNode::getValueFromXMLNodeContent(doc, cursor, tmpString);
1064 SeriesTime.putOFStringArray(tmpString);
1065 }
1066 else if (doc.getElementFromNodeContent(cursor, SeriesNumber, "number").bad() &&
1067 doc.getElementFromNodeContent(cursor, ProtocolName, "protocol", OFTrue /*encoding*/).bad() &&
1068 doc.getElementFromNodeContent(cursor, SeriesDescription, "description", OFTrue /*encoding*/).bad())
1069 {
1070 doc.printUnexpectedNodeWarning(cursor);
1071 }
1072 /* proceed with next node */
1073 cursor.gotoNext();
1074 }
1075 /* check required element values */
1076 checkElementValue(SeriesInstanceUID, "1", "1");
1077 checkElementValue(SeriesNumber, "1", "1");
1078 }
1079 return result;
1080 }
1081
1082
readXMLInstanceData(const DSRXMLDocument & doc,DSRXMLCursor cursor,const size_t flags)1083 OFCondition DSRDocument::readXMLInstanceData(const DSRXMLDocument &doc,
1084 DSRXMLCursor cursor,
1085 const size_t flags)
1086 {
1087 OFCondition result = SR_EC_InvalidDocument;
1088 if (cursor.valid())
1089 {
1090 OFString tmpString;
1091 /* get SOP Instance UID from XML attribute */
1092 if (flags & XF_acceptEmptyStudySeriesInstanceUID)
1093 {
1094 if (doc.getElementFromAttribute(cursor, SOPInstanceUID, "uid", OFFalse /*encoding*/, OFFalse /*required*/).bad())
1095 doc.printMissingAttributeWarning(cursor, "uid");
1096 result = EC_Normal;
1097 } else
1098 result = doc.getElementFromAttribute(cursor, SOPInstanceUID, "uid");
1099 /* goto first sub-element */
1100 cursor.gotoChild();
1101 /* iterate over all nodes */
1102 while (cursor.valid())
1103 {
1104 /* check for known element tags */
1105 if (doc.matchNode(cursor, "creation"))
1106 {
1107 /* Instance Creator UID */
1108 doc.getElementFromAttribute(cursor, InstanceCreatorUID, "uid", OFFalse /*encoding*/, OFFalse /*required*/);
1109 /* Instance Creation Date */
1110 DSRDateTreeNode::getValueFromXMLNodeContent(doc, doc.getNamedChildNode(cursor, "date"), tmpString);
1111 InstanceCreationDate.putOFStringArray(tmpString);
1112 /* Instance Creation Time */
1113 DSRTimeTreeNode::getValueFromXMLNodeContent(doc, doc.getNamedChildNode(cursor, "time"), tmpString);
1114 InstanceCreationTime.putOFStringArray(tmpString);
1115 }
1116 else if (doc.getElementFromNodeContent(cursor, InstanceNumber, "number").bad())
1117 doc.printUnexpectedNodeWarning(cursor);
1118 /* proceed with next node */
1119 cursor.gotoNext();
1120 }
1121 /* check required element values */
1122 checkElementValue(SOPInstanceUID, "1", "1");
1123 }
1124 return result;
1125 }
1126
1127
readXMLDocumentData(const DSRXMLDocument & doc,DSRXMLCursor cursor,const size_t flags)1128 OFCondition DSRDocument::readXMLDocumentData(const DSRXMLDocument &doc,
1129 DSRXMLCursor cursor,
1130 const size_t flags)
1131 {
1132 OFCondition result = SR_EC_InvalidDocument;
1133 if (cursor.valid())
1134 {
1135 OFString tmpString;
1136 /* check whether general SR modules are used */
1137 const OFBool usesGeneralSRModules = usesSRDocumentGeneralModule(getDocumentType());
1138 result = EC_Normal;
1139 /* iterate over all nodes */
1140 while (cursor.valid() && result.good())
1141 {
1142 /* check for known element tags
1143 (not all SR IODs contain the SR Document General Module) */
1144 if (usesGeneralSRModules && doc.matchNode(cursor, "preliminary"))
1145 {
1146 /* Preliminary Flag */
1147 PreliminaryFlagEnum = enumeratedValueToPreliminaryFlag(doc.getStringFromAttribute(cursor, tmpString, "flag"));
1148 if (PreliminaryFlagEnum == PF_invalid)
1149 printUnknownValueWarningMessage("PreliminaryFlag", tmpString.c_str());
1150 }
1151 else if (usesGeneralSRModules && doc.matchNode(cursor, "completion"))
1152 {
1153 /* Completion Flag */
1154 CompletionFlagEnum = enumeratedValueToCompletionFlag(doc.getStringFromAttribute(cursor, tmpString, "flag"));
1155 if (CompletionFlagEnum != CF_invalid)
1156 {
1157 /* Completion Flag Description (optional) */
1158 const DSRXMLCursor childCursor = doc.getNamedChildNode(cursor, "description", OFFalse /*required*/);
1159 if (childCursor.valid())
1160 doc.getElementFromNodeContent(childCursor, CompletionFlagDescription, NULL /*name*/, OFTrue /*encoding*/);
1161 } else
1162 printUnknownValueWarningMessage("CompletionFlag", tmpString.c_str());
1163 }
1164 else if (usesGeneralSRModules && doc.matchNode(cursor, "verification"))
1165 {
1166 /* Verification Flag */
1167 VerificationFlagEnum = enumeratedValueToVerificationFlag(doc.getStringFromAttribute(cursor, tmpString, "flag"));
1168 if (VerificationFlagEnum != VF_invalid)
1169 {
1170 /* Verifying Observers (required if VERIFIED) */
1171 result = readXMLVerifyingObserverData(doc, cursor.getChild(), flags);
1172 /* allow absence in case of UNVERIFIED */
1173 if (VerificationFlagEnum == VF_Unverified)
1174 result = EC_Normal;
1175 } else
1176 printUnknownValueWarningMessage("VerificationFlag", tmpString.c_str());
1177 }
1178 else if (usesGeneralSRModules && doc.matchNode(cursor, "predecessor"))
1179 {
1180 /* Predecessor Documents Sequence (optional) */
1181 result = PredecessorDocuments.readXML(doc, cursor.getChild(), flags);
1182 }
1183 else if (doc.matchNode(cursor, "identical"))
1184 {
1185 /* Identical Documents Sequence (optional) */
1186 result = IdenticalDocuments.readXML(doc, cursor.getChild(), flags);
1187 }
1188 else if (doc.matchNode(cursor, "content"))
1189 {
1190 /* Content Date */
1191 DSRDateTreeNode::getValueFromXMLNodeContent(doc, doc.getNamedChildNode(cursor, "date"), tmpString);
1192 ContentDate.putOFStringArray(tmpString);
1193 /* Content Time */
1194 DSRTimeTreeNode::getValueFromXMLNodeContent(doc, doc.getNamedChildNode(cursor, "time"), tmpString);
1195 ContentTime.putOFStringArray(tmpString);
1196 /* proceed with document tree */
1197 result = DocumentTree.readXML(doc, cursor.getChild(), flags);
1198 } else
1199 doc.printUnexpectedNodeWarning(cursor);
1200 /* print node error message (if any) */
1201 doc.printGeneralNodeError(cursor, result);
1202 /* proceed with next node */
1203 cursor.gotoNext();
1204 }
1205 }
1206 return result;
1207 }
1208
1209
readXMLVerifyingObserverData(const DSRXMLDocument & doc,DSRXMLCursor cursor,const size_t flags)1210 OFCondition DSRDocument::readXMLVerifyingObserverData(const DSRXMLDocument &doc,
1211 DSRXMLCursor cursor,
1212 const size_t flags)
1213 {
1214 OFCondition result = SR_EC_InvalidDocument;
1215 if (cursor.valid())
1216 {
1217 result = EC_Normal;
1218 /* iterate over all nodes */
1219 while (cursor.valid())
1220 {
1221 /* check for known element tags */
1222 if (doc.matchNode(cursor, "observer"))
1223 {
1224 DcmItem *ditem = new DcmItem();
1225 if (ditem != NULL)
1226 {
1227 OFString dateTimeString, nameString, orgaString;
1228 DSRCodedEntryValue codeValue;
1229 DSRXMLCursor childCursor = cursor.getChild();
1230 /* iterate over all child nodes */
1231 while (childCursor.valid())
1232 {
1233 /* check for known element tags */
1234 if (doc.matchNode(childCursor, "code"))
1235 {
1236 /* Verifying Observer Code */
1237 codeValue.readXML(doc, childCursor, flags);
1238 }
1239 else if (doc.matchNode(childCursor, "name"))
1240 {
1241 /* Verifying Observer Name */
1242 DSRPNameTreeNode::getValueFromXMLNodeContent(doc, childCursor.getChild(), nameString);
1243 }
1244 else if (doc.matchNode(childCursor, "datetime"))
1245 {
1246 /* Verification DateTime */
1247 DSRDateTimeTreeNode::getValueFromXMLNodeContent(doc, childCursor, dateTimeString);
1248 } else {
1249 /* Verifying Observer Organization */
1250 doc.getStringFromNodeContent(childCursor, orgaString, "organization", OFTrue /*encoding*/, OFFalse /*clearString*/);
1251 }
1252 /* proceed with next node */
1253 childCursor.gotoNext();
1254 }
1255 /* put string values into the sequence item */
1256 putStringValueToDataset(*ditem, DCM_VerificationDateTime, dateTimeString);
1257 putStringValueToDataset(*ditem, DCM_VerifyingObserverName, nameString);
1258 putStringValueToDataset(*ditem, DCM_VerifyingOrganization, orgaString);
1259 /* write code value to sequence item (might be empty, type 2) */
1260 codeValue.writeSequence(*ditem, DCM_VerifyingObserverIdentificationCodeSequence);
1261 /* insert items into sequence */
1262 VerifyingObserver.insert(ditem);
1263 }
1264 } else
1265 doc.printUnexpectedNodeWarning(cursor);
1266 /* proceed with next node */
1267 cursor.gotoNext();
1268 }
1269 }
1270 return result;
1271 }
1272
1273
writeXML(STD_NAMESPACE ostream & stream,const size_t flags)1274 OFCondition DSRDocument::writeXML(STD_NAMESPACE ostream &stream,
1275 const size_t flags)
1276 {
1277 OFCondition result = SR_EC_InvalidDocument;
1278 /* only write valid documents */
1279 if (isValid())
1280 {
1281 DCMSR_DEBUG("Writing SR document to XML format");
1282 /* used for multiple purposes */
1283 OFString tmpString;
1284 /* update all DICOM attributes */
1285 updateAttributes();
1286 /* check whether general SR modules are used */
1287 const OFBool usesGeneralSRModules = usesSRDocumentGeneralModule(getDocumentType());
1288
1289 // --- XML document structure (start) ---
1290
1291 stream << "<?xml version=\"1.0\"";
1292 /* optional character set */
1293 tmpString = characterSetToXMLName(SpecificCharacterSetEnum);
1294 if (!tmpString.empty() && (tmpString != "?"))
1295 stream << " encoding=\"" << tmpString << "\"";
1296 else if (!SpecificCharacterSet.isEmpty())
1297 DCMSR_WARN("Cannot map Specific Character Set to equivalent XML encoding");
1298 stream << "?>" << OFendl;
1299
1300 stream << "<report";
1301 /* optional namespace declaration */
1302 if (flags & XF_useDcmsrNamespace)
1303 stream << " xmlns=\"" << DCMSR_XML_NAMESPACE_URI << "\"";
1304 /* optional XML Schema reference */
1305 if (flags & XF_addSchemaReference)
1306 {
1307 if (flags & XF_useDcmsrNamespace)
1308 stream << OFendl << " ";
1309 stream << " xmlns:xsi=\"" XML_SCHEMA_INSTANCE_URI "\"" << OFendl << " "
1310 << " xsi:noNamespaceSchemaLocation=\"" DCMSR_XML_XSD_FILE "\"" << OFendl << " ";
1311 }
1312 stream << " type=\"" << documentTypeToReadableName(getDocumentType()) << "\">" << OFendl;
1313
1314 // --- write some general document information ---
1315
1316 stream << "<sopclass uid=\"" << getMarkupStringFromElement(SOPClassUID, tmpString) << "\">";
1317 /* retrieve name of SOP class (if known) */
1318 stream << dcmFindNameOfUID(tmpString.c_str(), "" /* empty value as default */);
1319 stream << "</sopclass>" << OFendl;
1320 writeStringFromElementToXML(stream, SpecificCharacterSet, "charset", (flags & XF_writeEmptyTags) > 0);
1321 writeStringFromElementToXML(stream, TimezoneOffsetFromUTC, "timezone", (flags & XF_writeEmptyTags) > 0);
1322 writeStringFromElementToXML(stream, Modality, "modality", (flags & XF_writeEmptyTags) > 0);
1323 /* check for additional device information */
1324 if (!ManufacturerModelName.isEmpty())
1325 {
1326 stream << "<device>" << OFendl;
1327 writeStringFromElementToXML(stream, Manufacturer, "manufacturer", (flags & XF_writeEmptyTags) > 0);
1328 writeStringFromElementToXML(stream, ManufacturerModelName, "model", (flags & XF_writeEmptyTags) > 0);
1329 writeStringFromElementToXML(stream, DeviceSerialNumber, "serial", (flags & XF_writeEmptyTags) > 0);
1330 writeStringFromElementToXML(stream, SoftwareVersions, "version", (flags & XF_writeEmptyTags) > 0);
1331 stream << "</device>" << OFendl;
1332 } else
1333 writeStringFromElementToXML(stream, Manufacturer, "manufacturer", (flags & XF_writeEmptyTags) > 0);
1334
1335 if ((flags & XF_writeEmptyTags) || !SynchronizationFrameOfReferenceUID.isEmpty() ||
1336 !SynchronizationTrigger.isEmpty() || !AcquisitionTimeSynchronized.isEmpty())
1337 {
1338 stream << "<synchronization";
1339 if (!SynchronizationFrameOfReferenceUID.isEmpty())
1340 stream << " uid=\"" << getMarkupStringFromElement(SynchronizationFrameOfReferenceUID, tmpString) << "\"";
1341 stream << ">" << OFendl;
1342 writeStringFromElementToXML(stream, SynchronizationTrigger, "trigger", (flags & XF_writeEmptyTags) > 0);
1343 writeStringFromElementToXML(stream, AcquisitionTimeSynchronized, "acquisitiontime", (flags & XF_writeEmptyTags) > 0);
1344 stream << "</synchronization>" << OFendl;
1345 }
1346
1347 if ((flags & XF_writeEmptyTags) || !ReferringPhysicianName.isEmpty())
1348 {
1349 stream << "<referringphysician>" << OFendl;
1350 writeStringFromElementToXML(stream, ReferringPhysicianName, "name", (flags & XF_writeEmptyTags) > 0);
1351 stream << "</referringphysician>" << OFendl;
1352 }
1353
1354 stream << "<patient>" << OFendl;
1355 writeStringFromElementToXML(stream, PatientID, "id", (flags & XF_writeEmptyTags) > 0);
1356 writeStringFromElementToXML(stream, IssuerOfPatientID, "issuer", (flags & XF_writeEmptyTags) > 0);
1357 writeStringFromElementToXML(stream, PatientName, "name", (flags & XF_writeEmptyTags) > 0);
1358 if ((flags & XF_writeEmptyTags) || !PatientBirthDate.isEmpty())
1359 {
1360 stream << "<birthday>" << OFendl;
1361 PatientBirthDate.getISOFormattedDate(tmpString);
1362 writeStringValueToXML(stream, tmpString, "date", (flags & XF_writeEmptyTags) > 0);
1363 stream << "</birthday>" << OFendl;
1364 }
1365 writeStringFromElementToXML(stream, PatientSex, "sex", (flags & XF_writeEmptyTags) > 0);
1366 stream << "</patient>" << OFendl;
1367
1368 stream << "<study uid=\"" << getMarkupStringFromElement(StudyInstanceUID, tmpString) << "\">" << OFendl;
1369 writeStringFromElementToXML(stream, StudyID, "id", (flags & XF_writeEmptyTags) > 0);
1370 StudyDate.getISOFormattedDate(tmpString);
1371 writeStringValueToXML(stream, tmpString, "date", (flags & XF_writeEmptyTags) > 0);
1372 StudyTime.getISOFormattedTime(tmpString);
1373 writeStringValueToXML(stream, tmpString, "time", (flags & XF_writeEmptyTags) > 0);
1374 if ((flags & XF_writeEmptyTags) || !AccessionNumber.isEmpty())
1375 {
1376 stream << "<accession>" << OFendl;
1377 writeStringFromElementToXML(stream, AccessionNumber, "number", (flags & XF_writeEmptyTags) > 0);
1378 stream << "</accession>" << OFendl;
1379 }
1380 writeStringFromElementToXML(stream, StudyDescription, "description", (flags & XF_writeEmptyTags) > 0);
1381 stream << "</study>" << OFendl;
1382
1383 stream << "<series uid=\"" << getMarkupStringFromElement(SeriesInstanceUID, tmpString) << "\">" << OFendl;
1384 writeStringFromElementToXML(stream, SeriesNumber, "number", (flags & XF_writeEmptyTags) > 0);
1385 SeriesDate.getISOFormattedDate(tmpString);
1386 writeStringValueToXML(stream, tmpString, "date", (flags & XF_writeEmptyTags) > 0);
1387 SeriesTime.getISOFormattedTime(tmpString);
1388 writeStringValueToXML(stream, tmpString, "time", (flags & XF_writeEmptyTags) > 0);
1389 writeStringFromElementToXML(stream, ProtocolName, "protocol", (flags & XF_writeEmptyTags) > 0);
1390 writeStringFromElementToXML(stream, SeriesDescription, "description", (flags & XF_writeEmptyTags) > 0);
1391 stream << "</series>" << OFendl;
1392
1393 stream << "<instance uid=\"" << getMarkupStringFromElement(SOPInstanceUID, tmpString) << "\">" << OFendl;
1394 writeStringFromElementToXML(stream, InstanceNumber, "number", (flags & XF_writeEmptyTags) > 0);
1395 if ((flags & XF_writeEmptyTags) || !InstanceCreatorUID.isEmpty() ||
1396 !InstanceCreationDate.isEmpty() || !InstanceCreationTime.isEmpty())
1397 {
1398 stream << "<creation";
1399 if (!InstanceCreatorUID.isEmpty())
1400 stream << " uid=\"" << getMarkupStringFromElement(InstanceCreatorUID, tmpString) << "\"";
1401 stream << ">" << OFendl;
1402 InstanceCreationDate.getISOFormattedDate(tmpString);
1403 writeStringValueToXML(stream, tmpString, "date", (flags & XF_writeEmptyTags) > 0);
1404 InstanceCreationTime.getISOFormattedTime(tmpString);
1405 writeStringValueToXML(stream, tmpString, "time", (flags & XF_writeEmptyTags) > 0);
1406 stream << "</creation>" << OFendl;
1407 }
1408 stream << "</instance>" << OFendl;
1409
1410 if ((flags & XF_writeEmptyTags) || !CodingSchemeIdentification.isEmpty())
1411 {
1412 stream << "<coding>" << OFendl;
1413 CodingSchemeIdentification.writeXML(stream, flags);
1414 stream << "</coding>" << OFendl;
1415 }
1416 if ((flags & XF_writeEmptyTags) || !CurrentRequestedProcedureEvidence.isEmpty())
1417 {
1418 stream << "<evidence type=\"Current Requested Procedure\">" << OFendl;
1419 CurrentRequestedProcedureEvidence.writeXML(stream, flags);
1420 stream << "</evidence>" << OFendl;
1421 }
1422 if (usesGeneralSRModules)
1423 {
1424 if ((flags & XF_writeEmptyTags) || !PertinentOtherEvidence.isEmpty())
1425 {
1426 stream << "<evidence type=\"Pertinent Other\">" << OFendl;
1427 PertinentOtherEvidence.writeXML(stream, flags);
1428 stream << "</evidence>" << OFendl;
1429 }
1430 if ((flags & XF_writeEmptyTags) || !ReferencedInstances.isEmpty())
1431 {
1432 stream << "<reference>" << OFendl;
1433 ReferencedInstances.writeXML(stream, flags);
1434 stream << "</reference>" << OFendl;
1435 }
1436 }
1437
1438 stream << "<document>" << OFendl;
1439 if (usesGeneralSRModules)
1440 {
1441 if (!PreliminaryFlag.isEmpty())
1442 stream << "<preliminary flag=\"" << getStringValueFromElement(PreliminaryFlag, tmpString) << "\"/>" << OFendl;
1443 stream << "<completion flag=\"" << getStringValueFromElement(CompletionFlag, tmpString) << "\">" << OFendl;
1444 writeStringFromElementToXML(stream, CompletionFlagDescription, "description", (flags & XF_writeEmptyTags) > 0);
1445 stream << "</completion>" << OFendl;
1446
1447 stream << "<verification flag=\"" << getStringValueFromElement(VerificationFlag, tmpString) << "\">" << OFendl;
1448 const size_t obsCount = getNumberOfVerifyingObservers();
1449 for (size_t i = 1; i <= obsCount; i++)
1450 {
1451 stream << "<observer pos=\"" << i << "\">" << OFendl;
1452 DSRCodedEntryValue obsCode;
1453 OFString dateTime, obsName, organization;
1454 if (getVerifyingObserver(i, dateTime, obsName, obsCode, organization).good())
1455 {
1456 /* output time in ISO 8601 format */
1457 DcmDateTime::getISOFormattedDateTimeFromString(dateTime, tmpString, OFTrue /*seconds*/, OFFalse /*fraction*/,
1458 OFTrue /*timeZone*/, OFFalse /*createMissingPart*/, "T" /*dateTimeSeparator*/, "" /*timeZoneSeparator*/);
1459 writeStringValueToXML(stream, tmpString, "datetime", (flags & XF_writeEmptyTags) > 0);
1460 if (!obsName.empty() || (flags & XF_writeEmptyTags))
1461 stream << "<name>" << OFendl << dicomToXMLPersonName(obsName, tmpString) << OFendl << "</name>" << OFendl;
1462 if (obsCode.isValid())
1463 {
1464 if (flags & DSRTypes::XF_codeComponentsAsAttribute)
1465 stream << "<code"; // bracket ">" is closed in next writeXML() call
1466 else
1467 stream << "<code>" << OFendl;
1468 obsCode.writeXML(stream, flags);
1469 stream << "</code>" << OFendl;
1470 }
1471 writeStringValueToXML(stream, organization, "organization", (flags & XF_writeEmptyTags) > 0);
1472 }
1473 stream << "</observer>" << OFendl;
1474 }
1475 stream << "</verification>" << OFendl;
1476
1477 if ((flags & XF_writeEmptyTags) || !PredecessorDocuments.isEmpty())
1478 {
1479 stream << "<predecessor>" << OFendl;
1480 PredecessorDocuments.writeXML(stream, flags);
1481 stream << "</predecessor>" << OFendl;
1482 }
1483 }
1484 if ((flags & XF_writeEmptyTags) || !IdenticalDocuments.isEmpty())
1485 {
1486 stream << "<identical>" << OFendl;
1487 IdenticalDocuments.writeXML(stream, flags);
1488 stream << "</identical>" << OFendl;
1489 }
1490
1491 // --- write document content/tree to stream ---
1492
1493 stream << "<content>" << OFendl;
1494 ContentDate.getISOFormattedDate(tmpString);
1495 writeStringValueToXML(stream, tmpString, "date", (flags & XF_writeEmptyTags) > 0);
1496 ContentTime.getISOFormattedTime(tmpString);
1497 writeStringValueToXML(stream, tmpString, "time", (flags & XF_writeEmptyTags) > 0);
1498 result = DocumentTree.writeXML(stream, flags);
1499 stream << "</content>" << OFendl;
1500 stream << "</document>" << OFendl;
1501
1502 // --- XML document structure (end) ---
1503
1504 stream << "</report>" << OFendl;
1505 }
1506 return result;
1507 }
1508
1509
renderHTMLPatientData(STD_NAMESPACE ostream & stream,const size_t flags)1510 void DSRDocument::renderHTMLPatientData(STD_NAMESPACE ostream &stream,
1511 const size_t flags)
1512 {
1513 OFString tmpString, string2;
1514 OFString htmlString;
1515 stream << convertToHTMLString(dicomToReadablePersonName(getStringValueFromElement(PatientName, tmpString), string2), htmlString, flags);
1516 OFString patientStr;
1517 if (!PatientSex.isEmpty())
1518 {
1519 getPrintStringFromElement(PatientSex, tmpString);
1520 if (tmpString == "M")
1521 patientStr += "male";
1522 else if (tmpString == "F")
1523 patientStr += "female";
1524 else if (tmpString == "O")
1525 patientStr += "other";
1526 else
1527 patientStr += convertToHTMLString(tmpString, htmlString, flags);
1528 }
1529 if (!PatientBirthDate.isEmpty())
1530 {
1531 if (!patientStr.empty())
1532 patientStr += ", ";
1533 patientStr += '*';
1534 patientStr += dicomToReadableDate(getStringValueFromElement(PatientBirthDate, tmpString), string2);
1535 }
1536 if (!PatientID.isEmpty())
1537 {
1538 if (!patientStr.empty())
1539 patientStr += ", ";
1540 patientStr += '#';
1541 patientStr += convertToHTMLString(getStringValueFromElement(PatientID, tmpString), htmlString, flags);
1542 if (!IssuerOfPatientID.isEmpty())
1543 {
1544 patientStr += ":";
1545 patientStr += convertToHTMLString(getStringValueFromElement(IssuerOfPatientID, tmpString), htmlString, flags);
1546 }
1547 }
1548 if (!patientStr.empty())
1549 stream << " (" << patientStr << ")";
1550 }
1551
1552
renderHTMLReferenceList(STD_NAMESPACE ostream & stream,DSRSOPInstanceReferenceList & refList,const size_t flags)1553 void DSRDocument::renderHTMLReferenceList(STD_NAMESPACE ostream &stream,
1554 DSRSOPInstanceReferenceList &refList,
1555 const size_t flags)
1556 {
1557 /* goto first list item (if not empty) */
1558 if (refList.gotoFirstItem().good())
1559 {
1560 OFString tmpString;
1561 DSRCodedEntryValue codeValue;
1562 unsigned int i = 0;
1563 /* iterate over all list items */
1564 do {
1565 if (i > 0)
1566 {
1567 stream << "</tr>" << OFendl;
1568 stream << "<tr>" << OFendl;
1569 stream << "<td></td>" << OFendl;
1570 }
1571 /* hyperlink to composite object */
1572 OFString sopClass, sopInstance;
1573 if (!refList.getSOPClassUID(sopClass).empty() && !refList.getSOPInstanceUID(sopInstance).empty())
1574 {
1575 stream << "<td><a href=\"" << HTML_HYPERLINK_PREFIX_FOR_CGI;
1576 stream << "?composite=" << sopClass << "+" << sopInstance << "\">";
1577 /* check whether referenced object has a well-known SOP class */
1578 stream << dcmFindNameOfUID(sopClass.c_str(), "unknown composite object");
1579 stream << "</a>";
1580 /* try to get the purpose of reference code, which is optional */
1581 if (refList.getPurposeOfReference(codeValue).good() && !codeValue.getCodeMeaning().empty())
1582 stream << " (" << DSRTypes::convertToHTMLString(codeValue.getCodeMeaning(), tmpString, flags) << ")";
1583 stream << "</td>" << OFendl;
1584 } else
1585 stream << "<td><i>invalid object reference</i></td>" << OFendl;
1586 i++;
1587 } while (refList.gotoNextItem().good());
1588 }
1589 }
1590
1591
renderHTMLReferenceList(STD_NAMESPACE ostream & stream,DSRReferencedInstanceList & refList,const size_t flags)1592 void DSRDocument::renderHTMLReferenceList(STD_NAMESPACE ostream &stream,
1593 DSRReferencedInstanceList &refList,
1594 const size_t flags)
1595 {
1596 /* goto first list item (if not empty) */
1597 if (refList.gotoFirstItem().good())
1598 {
1599 OFString tmpString;
1600 DSRCodedEntryValue codeValue;
1601 unsigned int i = 0;
1602 /* iterate over all list items */
1603 do {
1604 if (i > 0)
1605 {
1606 stream << "</tr>" << OFendl;
1607 stream << "<tr>" << OFendl;
1608 stream << "<td></td>" << OFendl;
1609 }
1610 /* hyperlink to composite object */
1611 OFString sopClass, sopInstance;
1612 if (!refList.getSOPClassUID(sopClass).empty() && !refList.getSOPInstanceUID(sopInstance).empty())
1613 {
1614 stream << "<td><a href=\"" << HTML_HYPERLINK_PREFIX_FOR_CGI;
1615 stream << "?composite=" << sopClass << "+" << sopInstance << "\">";
1616 /* retrieve name of SOP class (if known) */
1617 stream << dcmFindNameOfUID(sopClass.c_str(), "unknown composite object");
1618 stream << "</a>";
1619 /* try to get the purpose of reference code (at least the code meaning) */
1620 if (refList.getPurposeOfReference(codeValue).good() && !codeValue.getCodeMeaning().empty())
1621 stream << " (" << DSRTypes::convertToHTMLString(codeValue.getCodeMeaning(), tmpString, flags) << ")";
1622 stream << "</td>" << OFendl;
1623 } else
1624 stream << "<td><i>invalid reference</i></td>" << OFendl;
1625 i++;
1626 } while (refList.gotoNextItem().good());
1627 }
1628 }
1629
1630
renderHTML(STD_NAMESPACE ostream & stream,const size_t flags,const char * styleSheet)1631 OFCondition DSRDocument::renderHTML(STD_NAMESPACE ostream &stream,
1632 const size_t flags,
1633 const char *styleSheet)
1634 {
1635 OFCondition result = SR_EC_InvalidDocument;
1636 /* only render valid documents */
1637 if (isValid())
1638 {
1639 size_t newFlags = flags;
1640 if (flags & HF_XHTML11Compatibility)
1641 newFlags = (flags & ~DSRTypes::HF_HTML32Compatibility);
1642 else if (flags & HF_HTML32Compatibility)
1643 {
1644 /* fixes for HTML 3.2 */
1645 newFlags = (flags & ~HF_useCodeDetailsTooltip) | HF_convertNonASCIICharacters;
1646 /* ignore CSS (if any) */
1647 styleSheet = NULL;
1648 }
1649
1650 /* used for multiple purposes */
1651 OFString tmpString, string2;
1652 /* used for HTML tmpString conversion */
1653 OFString htmlString;
1654 /* update only some DICOM attributes */
1655 updateAttributes(OFFalse /*updateAll*/);
1656 /* check whether general SR modules are used */
1657 const OFBool usesGeneralSRModules = usesSRDocumentGeneralModule(getDocumentType());
1658
1659 // --- HTML/XHTML document structure (start) ---
1660
1661 if (newFlags & HF_XHTML11Compatibility)
1662 {
1663 stream << "<?xml version=\"1.0\"";
1664 /* optional character set */
1665 tmpString = characterSetToXMLName(SpecificCharacterSetEnum);
1666 if (!tmpString.empty())
1667 {
1668 if (tmpString != "?")
1669 stream << " encoding=\"" << tmpString << "\"";
1670 else
1671 DCMSR_WARN("Cannot map Specific Character Set to equivalent XML encoding");
1672 }
1673 stream << "?>" << OFendl;
1674 }
1675
1676 /* optional document type definition */
1677 if (newFlags & HF_addDocumentTypeReference)
1678 {
1679 if (newFlags & HF_XHTML11Compatibility)
1680 stream << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">" << OFendl;
1681 else {
1682 if (newFlags & HF_HTML32Compatibility)
1683 stream << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2//EN\">" << OFendl;
1684 else
1685 stream << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" << OFendl;
1686 }
1687 }
1688 stream << "<html";
1689 if (newFlags & HF_XHTML11Compatibility)
1690 stream << " xmlns=\"http://www.w3.org/1999/xhtml\"";
1691 stream << ">" << OFendl;
1692 stream << "<head>" << OFendl;
1693 /* document title */
1694 stream << "<title>";
1695 if (newFlags & HF_renderPatientTitle)
1696 renderHTMLPatientData(stream, newFlags);
1697 else
1698 stream << documentTypeToDocumentTitle(getDocumentType(), tmpString);
1699 stream << "</title>" << OFendl;
1700 /* for HTML 4.01 and XHTML 1.1 only */
1701 if (!(newFlags & HF_HTML32Compatibility))
1702 {
1703 /* optional cascading style sheet */
1704 if (styleSheet != NULL)
1705 {
1706 if (newFlags & HF_copyStyleSheetContent)
1707 {
1708 /* copy content from CSS file */
1709 STD_NAMESPACE ifstream cssFile(styleSheet, OFopenmode_in_nocreate);
1710 if (cssFile)
1711 {
1712 char c;
1713 stream << "<style type=\"text/css\">" << OFendl;
1714 stream << "<!--" << OFendl;
1715 /* copy all characters */
1716 while (cssFile.get(c))
1717 stream << c;
1718 stream << "//-->" << OFendl;
1719 stream << "</style>" << OFendl;
1720 } else
1721 DCMSR_WARN("Could not open CSS file \"" << styleSheet << "\" ... ignoring");
1722 } else {
1723 /* just add a reference to the CSS file (might be an URL) */
1724 stream << "<link rel=\"stylesheet\" type=\"text/css\" href=\"" << styleSheet << "\"";
1725 if (newFlags & HF_XHTML11Compatibility)
1726 stream << " /";
1727 stream << ">" << OFendl;
1728 }
1729 }
1730 /* optional character set */
1731 tmpString = characterSetToHTMLName(SpecificCharacterSetEnum);
1732 if (!tmpString.empty())
1733 {
1734 if (tmpString != "?")
1735 {
1736 stream << "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=" << tmpString << "\"";
1737 if (newFlags & HF_XHTML11Compatibility)
1738 stream << " /";
1739 stream << ">" << OFendl;
1740 } else
1741 DCMSR_WARN("Cannot map Specific Character Set to equivalent HTML charset");
1742 }
1743 }
1744 /* generator meta element referring to the DCMTK */
1745 if (!(newFlags & HF_omitGeneratorMetaElement))
1746 {
1747 stream << "<meta name=\"generator\" content=\"OFFIS DCMTK " << OFFIS_DCMTK_VERSION << "\"";
1748 if (newFlags & HF_XHTML11Compatibility)
1749 stream << " /";
1750 stream << ">" << OFendl;
1751 }
1752 stream << "</head>" << OFendl;
1753 stream << "<body>" << OFendl;
1754
1755 // --- render some general document information ---
1756
1757 if (!(newFlags & HF_renderNoDocumentHeader))
1758 {
1759 /* create a table for this purpose */
1760 stream << "<table>" << OFendl;
1761 /* patient related information */
1762 if (!PatientName.isEmpty())
1763 {
1764 stream << "<tr>" << OFendl;
1765 stream << "<td><b>Patient:</b></td>" << OFendl;
1766 stream << "<td>";
1767 renderHTMLPatientData(stream, newFlags);
1768 stream << "</td>" << OFendl;
1769 stream << "</tr>" << OFendl;
1770 }
1771 /* referring physician */
1772 if (!ReferringPhysicianName.isEmpty())
1773 {
1774 stream << "<tr>" << OFendl;
1775 stream << "<td><b>Referring Physician:</b></td>" << OFendl;
1776 stream << "<td>" << convertToHTMLString(dicomToReadablePersonName(getStringValueFromElement(ReferringPhysicianName, tmpString), string2), htmlString, newFlags);
1777 stream << "</td>" << OFendl;
1778 stream << "</tr>" << OFendl;
1779 }
1780 /* study-related information */
1781 if (!StudyDescription.isEmpty())
1782 {
1783 stream << "<tr>" << OFendl;
1784 stream << "<td><b>Study:</b></td>" << OFendl;
1785 stream << "<td>" << convertToHTMLString(getStringValueFromElement(StudyDescription, tmpString), htmlString, newFlags);
1786 if (!StudyID.isEmpty())
1787 stream << " (#" << convertToHTMLString(getStringValueFromElement(StudyID, tmpString), htmlString, newFlags) << ")";
1788 stream << "</td>" << OFendl;
1789 stream << "</tr>" << OFendl;
1790 }
1791 /* series-related information */
1792 if (!SeriesDescription.isEmpty())
1793 {
1794 stream << "<tr>" << OFendl;
1795 stream << "<td><b>Series:</b></td>" << OFendl;
1796 stream << "<td>" << convertToHTMLString(getStringValueFromElement(SeriesDescription, tmpString), htmlString, newFlags);
1797 if (!SeriesNumber.isEmpty())
1798 stream << " (#" << convertToHTMLString(getStringValueFromElement(SeriesNumber, tmpString), htmlString, newFlags) << ")";
1799 stream << "</td>" << OFendl;
1800 stream << "</tr>" << OFendl;
1801 }
1802 /* protocol name */
1803 if (!ProtocolName.isEmpty())
1804 {
1805 stream << "<tr>" << OFendl;
1806 stream << "<td><b>Protocol:</b></td>" << OFendl;
1807 stream << "<td>" << convertToHTMLString(getStringValueFromElement(ProtocolName, tmpString), htmlString, newFlags) << "</td>" << OFendl;
1808 stream << "</tr>" << OFendl;
1809 }
1810 /* manufacturer */
1811 if (!Manufacturer.isEmpty())
1812 {
1813 stream << "<tr>" << OFendl;
1814 stream << "<td><b>Manufacturer:</b></td>" << OFendl;
1815 stream << "<td>" << convertToHTMLString(getStringValueFromElement(Manufacturer, tmpString), htmlString, newFlags);
1816 OFString deviceStr;
1817 if (!ManufacturerModelName.isEmpty())
1818 deviceStr += convertToHTMLString(getStringValueFromElement(ManufacturerModelName, tmpString), htmlString, newFlags);
1819 if (!DeviceSerialNumber.isEmpty())
1820 {
1821 if (!deviceStr.empty())
1822 deviceStr += ", ";
1823 deviceStr += '#';
1824 deviceStr += convertToHTMLString(getStringValueFromElement(DeviceSerialNumber, tmpString), htmlString, newFlags);
1825 }
1826 if (!deviceStr.empty())
1827 stream << " (" << deviceStr << ")";
1828 stream << "</td>" << OFendl;
1829 stream << "</tr>" << OFendl;
1830 }
1831 if (usesGeneralSRModules)
1832 {
1833 /* preliminary flag */
1834 if (!PreliminaryFlag.isEmpty())
1835 {
1836 stream << "<tr>" << OFendl;
1837 stream << "<td><b>Preliminary Flag:</b></td>" << OFendl;
1838 stream << "<td>" << getStringValueFromElement(PreliminaryFlag, tmpString) << "</td>" << OFendl;
1839 stream << "</tr>" << OFendl;
1840 }
1841 /* completion flag */
1842 stream << "<tr>" << OFendl;
1843 stream << "<td><b>Completion Flag:</b></td>" << OFendl;
1844 stream << "<td>" << getStringValueFromElement(CompletionFlag, tmpString) << "</td>" << OFendl;
1845 stream << "</tr>" << OFendl;
1846 /* completion flag description */
1847 if (!CompletionFlagDescription.isEmpty())
1848 {
1849 stream << "<tr>" << OFendl;
1850 stream << "<td></td>" << OFendl;
1851 stream << "<td>" << convertToHTMLString(getStringValueFromElement(CompletionFlagDescription, tmpString), htmlString, newFlags);
1852 stream << "</td>" << OFendl;
1853 stream << "</tr>" << OFendl;
1854 }
1855 /* predecessor documents */
1856 if (!PredecessorDocuments.isEmpty())
1857 {
1858 stream << "<tr>" << OFendl;
1859 stream << "<td><b>Predecessor Docs:</b></td>" << OFendl;
1860 renderHTMLReferenceList(stream, PredecessorDocuments, flags);
1861 stream << "</tr>" << OFendl;
1862 }
1863 }
1864 /* identical documents */
1865 if (!IdenticalDocuments.isEmpty())
1866 {
1867 stream << "<tr>" << OFendl;
1868 stream << "<td><b>Identical Docs:</b></td>" << OFendl;
1869 renderHTMLReferenceList(stream, IdenticalDocuments, flags);
1870 stream << "</tr>" << OFendl;
1871 }
1872 /* referenced instances */
1873 if (!ReferencedInstances.isEmpty())
1874 {
1875 stream << "<tr>" << OFendl;
1876 stream << "<td><b>Referenced Objects:</b></td>" << OFendl;
1877 renderHTMLReferenceList(stream, ReferencedInstances, flags);
1878 stream << "</tr>" << OFendl;
1879 }
1880 if (usesGeneralSRModules)
1881 {
1882 /* verification flag */
1883 stream << "<tr>" << OFendl;
1884 stream << "<td><b>Verification Flag:</b></td>" << OFendl;
1885 stream << "<td>" << getStringValueFromElement(VerificationFlag, tmpString) << "</td>" << OFendl;
1886 stream << "</tr>" << OFendl;
1887 /* verifying observer */
1888 const size_t obsCount = getNumberOfVerifyingObservers();
1889 for (size_t i = 1; i <= obsCount; i++)
1890 {
1891 OFString dateTime, obsName, organization;
1892 DSRCodedEntryValue obsCode;
1893 if (getVerifyingObserver(i, dateTime, obsName, obsCode, organization).good())
1894 {
1895 stream << "<tr>" << OFendl;
1896 if (i == 1)
1897 stream << "<td><b>Verifying Observers:</b></td>" << OFendl;
1898 else
1899 stream << "<td></td>" << OFendl;
1900 stream << "<td>";
1901 stream << dicomToReadableDateTime(dateTime, string2) << " - ";
1902 /* render code details as a tooltip (HTML 4.01 or above) */
1903 if (obsCode.isValid() && (newFlags & DSRTypes::HF_useCodeDetailsTooltip))
1904 {
1905 if (newFlags & HF_XHTML11Compatibility)
1906 stream << "<span class=\"code\" title=\"(";
1907 else /* HTML 4.01 */
1908 stream << "<span class=\"under\" title=\"(";
1909 stream << convertToHTMLString(obsCode.getCodeValue(), htmlString, newFlags) << ", ";
1910 stream << convertToHTMLString(obsCode.getCodingSchemeDesignator(), htmlString, newFlags);
1911 if (!obsCode.getCodingSchemeVersion().empty())
1912 stream << " [" << convertToHTMLString(obsCode.getCodingSchemeVersion(), htmlString, newFlags) << "]";
1913 stream << ", "" << convertToHTMLString(obsCode.getCodeMeaning(), htmlString, newFlags) << "")\">";
1914 }
1915 stream << convertToHTMLString(dicomToReadablePersonName(obsName, string2), htmlString, newFlags);
1916 if (obsCode.isValid() && (newFlags & DSRTypes::HF_useCodeDetailsTooltip))
1917 stream << "</span>";
1918 else {
1919 /* render code in a conventional manner */
1920 if (obsCode.isValid() && ((newFlags & HF_renderAllCodes) == HF_renderAllCodes))
1921 {
1922 stream << " (" << convertToHTMLString(obsCode.getCodeValue(), htmlString, newFlags);
1923 stream << ", " << convertToHTMLString(obsCode.getCodingSchemeDesignator(), htmlString, newFlags);
1924 if (!obsCode.getCodingSchemeVersion().empty())
1925 stream << " [" << convertToHTMLString(obsCode.getCodingSchemeVersion(), htmlString, newFlags) << "]";
1926 stream << ", "" << convertToHTMLString(obsCode.getCodeMeaning(), htmlString, newFlags) << "")";
1927 }
1928 }
1929 stream << ", " << convertToHTMLString(organization, htmlString, newFlags);
1930 stream << "</td>" << OFendl;
1931 stream << "</tr>" << OFendl;
1932 }
1933 }
1934 }
1935 if (!ContentDate.isEmpty() && !ContentTime.isEmpty())
1936 {
1937 /* content date and time */
1938 stream << "<tr>" << OFendl;
1939 stream << "<td><b>Content Date/Time:</b></td>" << OFendl;
1940 stream << "<td>" << dicomToReadableDate(getStringValueFromElement(ContentDate, tmpString), string2) << " ";
1941 stream << dicomToReadableTime(getStringValueFromElement(ContentTime, tmpString), string2) << "</td>" << OFendl;
1942 stream << "</tr>" << OFendl;
1943 }
1944 /* end of table */
1945 stream << "</table>" << OFendl;
1946
1947 if (newFlags & HF_XHTML11Compatibility)
1948 stream << "<hr />" << OFendl;
1949 else
1950 stream << "<hr>" << OFendl;
1951 }
1952
1953 // --- render document tree to stream ---
1954
1955 /* create memory output stream for the annex */
1956 OFOStringStream annexStream;
1957 /* render document tree two the streams */
1958 result = DocumentTree.renderHTML(stream, annexStream, newFlags);
1959 /* append annex (with heading) to main document */
1960 if (result.good())
1961 result = appendStream(stream, annexStream, "<h1>Annex</h1>");
1962
1963 // --- footnote ---
1964
1965 if (newFlags & HF_renderDcmtkFootnote)
1966 {
1967 if (newFlags & HF_XHTML11Compatibility)
1968 {
1969 stream << "<hr />" << OFendl;
1970 stream << "<div class=\"footnote\">" << OFendl;
1971 } else {
1972 stream << "<hr>" << OFendl;
1973 if (newFlags & HF_HTML32Compatibility)
1974 stream << "<div>" << OFendl;
1975 else /* HTML 4.01 */
1976 stream << "<div class=\"footnote\">" << OFendl;
1977 stream << "<small>" << OFendl;
1978 }
1979 stream << "This page was generated from a DICOM Structured Reporting document by ";
1980 stream << "<a href=\"" << DCMTK_INTERNET_URL << "\">OFFIS DCMTK</a> " << OFFIS_DCMTK_VERSION << "." << OFendl;
1981 if (!(newFlags & HF_XHTML11Compatibility))
1982 stream << "</small>" << OFendl;
1983 stream << "</div>" << OFendl;
1984 }
1985
1986 // --- HTML document structure (end) ---
1987
1988 stream << "</body>" << OFendl;
1989 stream << "</html>" << OFendl;
1990 }
1991 return result;
1992 }
1993
1994
getDocumentType() const1995 DSRTypes::E_DocumentType DSRDocument::getDocumentType() const
1996 {
1997 return DocumentTree.getDocumentType();
1998 }
1999
2000
setTree(const DSRDocumentTree & tree)2001 OFCondition DSRDocument::setTree(const DSRDocumentTree &tree)
2002 {
2003 OFCondition result = SR_EC_InvalidDocumentTree;
2004 /* make sure that the new tree is valid */
2005 if (tree.isValid())
2006 {
2007 /* replace current tree with given one */
2008 DocumentTree = tree;
2009 /* update IOD-specific DICOM attributes */
2010 updateAttributes(OFFalse /*updateAll*/);
2011 result = EC_Normal;
2012 }
2013 return result;
2014 }
2015
2016
setTreeFromRootTemplate(DSRRootTemplate & rootTemplate,const OFBool expandTree)2017 OFCondition DSRDocument::setTreeFromRootTemplate(DSRRootTemplate &rootTemplate,
2018 const OFBool expandTree)
2019 {
2020 OFCondition result = EC_Normal;
2021 /* check whether to expand the included templates (if any) */
2022 if (expandTree)
2023 {
2024 DSRDocumentSubTree *tree = NULL;
2025 /* expand tree managed by the template and replace the currently stored tree */
2026 result = rootTemplate.getTree().createExpandedSubTree(tree);
2027 if (result.good())
2028 result = DocumentTree.changeDocumentType(rootTemplate.getDocumentType(), OFTrue /*deleteTree*/);
2029 if (result.good())
2030 result = DocumentTree.insertSubTree(tree, AM_belowCurrent, RT_unknown, OFFalse /*deleteIfFail*/);
2031 /* update IOD-specific DICOM attributes */
2032 updateAttributes(OFFalse /*updateAll*/);
2033 /* in case of error, free memory */
2034 if (result.bad())
2035 delete tree;
2036 } else {
2037 /* call the functions doing the real work */
2038 result = setTree(rootTemplate.getTree());
2039 }
2040 return result;
2041 }
2042
2043
getSpecificCharacterSet() const2044 const char *DSRDocument::getSpecificCharacterSet() const
2045 {
2046 /* never return NULL */
2047 return OFSTRING_GUARD(getStringValueFromElement(SpecificCharacterSet));
2048 }
2049
2050
getSpecificCharacterSetType() const2051 DSRTypes::E_CharacterSet DSRDocument::getSpecificCharacterSetType() const
2052 {
2053 return SpecificCharacterSetEnum;
2054 }
2055
2056
setSpecificCharacterSetType(const E_CharacterSet characterSet)2057 OFCondition DSRDocument::setSpecificCharacterSetType(const E_CharacterSet characterSet)
2058 {
2059 SpecificCharacterSetEnum = characterSet;
2060 return SpecificCharacterSet.putString(characterSetToDefinedTerm(SpecificCharacterSetEnum));
2061 }
2062
2063
getPreliminaryFlag() const2064 DSRTypes::E_PreliminaryFlag DSRDocument::getPreliminaryFlag() const
2065 {
2066 return PreliminaryFlagEnum;
2067 }
2068
2069
setPreliminaryFlag(const E_PreliminaryFlag flag)2070 OFCondition DSRDocument::setPreliminaryFlag(const E_PreliminaryFlag flag)
2071 {
2072 OFCondition result = EC_IllegalCall;
2073 /* not all SR IODs contain the SR Document General Module */
2074 if (usesSRDocumentGeneralModule(getDocumentType()))
2075 {
2076 PreliminaryFlagEnum = flag;
2077 result = EC_Normal;
2078 }
2079 return result;
2080 }
2081
2082
getCompletionFlag() const2083 DSRTypes::E_CompletionFlag DSRDocument::getCompletionFlag() const
2084 {
2085 return CompletionFlagEnum;
2086 }
2087
2088
getVerificationFlag() const2089 DSRTypes::E_VerificationFlag DSRDocument::getVerificationFlag() const
2090 {
2091 return VerificationFlagEnum;
2092 }
2093
2094
hasVerifyingObservers() const2095 OFBool DSRDocument::hasVerifyingObservers() const
2096 {
2097 return (VerifyingObserver.card() > 0);
2098 }
2099
2100
getNumberOfVerifyingObservers() const2101 size_t DSRDocument::getNumberOfVerifyingObservers() const
2102 {
2103 return OFstatic_cast(size_t, VerifyingObserver.card());
2104 }
2105
2106
getVerifyingObserver(const size_t idx,OFString & dateTime,OFString & observerName,OFString & organization)2107 OFCondition DSRDocument::getVerifyingObserver(const size_t idx,
2108 OFString &dateTime,
2109 OFString &observerName,
2110 OFString &organization)
2111 {
2112 DSRCodedEntryValue dummyCode;
2113 return getVerifyingObserver(idx, dateTime, observerName, dummyCode, organization);
2114 }
2115
2116
getVerifyingObserver(const size_t idx,OFString & dateTime,OFString & observerName,DSRCodedEntryValue & observerCode,OFString & organization)2117 OFCondition DSRDocument::getVerifyingObserver(const size_t idx,
2118 OFString &dateTime,
2119 OFString &observerName,
2120 DSRCodedEntryValue &observerCode,
2121 OFString &organization)
2122 {
2123 OFCondition result = EC_IllegalParameter;
2124 /* clear all reference variables */
2125 dateTime.clear();
2126 observerName.clear();
2127 observerCode.clear();
2128 organization.clear();
2129 /* get specified entry */
2130 if ((idx > 0) && (idx <= getNumberOfVerifyingObservers()))
2131 {
2132 /* access by index is currently not very efficient */
2133 DcmItem *ditem = VerifyingObserver.getItem(OFstatic_cast(unsigned long, idx - 1));
2134 if (ditem != NULL)
2135 {
2136 result = getStringValueFromDataset(*ditem, DCM_VerificationDateTime, dateTime);
2137 if (result.good())
2138 result = getStringValueFromDataset(*ditem, DCM_VerifyingObserverName, observerName);
2139 if (result.good())
2140 {
2141 /* code is optional (type 2) */
2142 observerCode.readSequence(*ditem, DCM_VerifyingObserverIdentificationCodeSequence, "2" /*type*/);
2143 result = getStringValueFromDataset(*ditem, DCM_VerifyingOrganization, organization);
2144 }
2145 if (result.good())
2146 {
2147 if (dateTime.empty() || observerName.empty() || organization.empty())
2148 result = SR_EC_InvalidValue;
2149 }
2150 }
2151 }
2152 return result;
2153 }
2154
2155
getPredecessorDocuments()2156 DSRSOPInstanceReferenceList &DSRDocument::getPredecessorDocuments()
2157 {
2158 /* pass current value of specific character set */
2159 PredecessorDocuments.setSpecificCharacterSet(getSpecificCharacterSet());
2160 return PredecessorDocuments;
2161 }
2162
2163
getIdenticalDocuments()2164 DSRSOPInstanceReferenceList &DSRDocument::getIdenticalDocuments()
2165 {
2166 /* pass current value of specific character set */
2167 IdenticalDocuments.setSpecificCharacterSet(getSpecificCharacterSet());
2168 return IdenticalDocuments;
2169 }
2170
2171
getCurrentRequestedProcedureEvidence()2172 DSRSOPInstanceReferenceList &DSRDocument::getCurrentRequestedProcedureEvidence()
2173 {
2174 /* pass current value of specific character set */
2175 CurrentRequestedProcedureEvidence.setSpecificCharacterSet(getSpecificCharacterSet());
2176 return CurrentRequestedProcedureEvidence;
2177 }
2178
2179
getPertinentOtherEvidence()2180 DSRSOPInstanceReferenceList &DSRDocument::getPertinentOtherEvidence()
2181 {
2182 /* pass current value of specific character set */
2183 PredecessorDocuments.setSpecificCharacterSet(getSpecificCharacterSet());
2184 return PertinentOtherEvidence;
2185 }
2186
2187
getReferencedInstances()2188 DSRReferencedInstanceList &DSRDocument::getReferencedInstances()
2189 {
2190 return ReferencedInstances;
2191 }
2192
2193
getCodingSchemeIdentification()2194 DSRCodingSchemeIdentificationList &DSRDocument::getCodingSchemeIdentification()
2195 {
2196 /* pass current value of specific character set */
2197 CodingSchemeIdentification.setSpecificCharacterSet(getSpecificCharacterSet());
2198 return CodingSchemeIdentification;
2199 }
2200
2201
2202 // --- get attributes (C++ string) ---
2203
getSpecificCharacterSet(OFString & value,const signed long pos) const2204 OFCondition DSRDocument::getSpecificCharacterSet(OFString &value,
2205 const signed long pos) const
2206 {
2207 return getStringValueFromElement(SpecificCharacterSet, value, pos);
2208 }
2209
2210
getCompletionFlagDescription(OFString & value,const signed long pos) const2211 OFCondition DSRDocument::getCompletionFlagDescription(OFString &value,
2212 const signed long pos) const
2213 {
2214 return getStringValueFromElement(CompletionFlagDescription, value, pos);
2215 }
2216
2217
getModality(OFString & value,const signed long pos) const2218 OFCondition DSRDocument::getModality(OFString &value,
2219 const signed long pos) const
2220 {
2221 return getStringValueFromElement(Modality, value, pos);
2222 }
2223
2224
getSOPClassUID(OFString & value,const signed long pos) const2225 OFCondition DSRDocument::getSOPClassUID(OFString &value,
2226 const signed long pos) const
2227 {
2228 return getStringValueFromElement(SOPClassUID, value, pos);
2229 }
2230
2231
getStudyInstanceUID(OFString & value,const signed long pos) const2232 OFCondition DSRDocument::getStudyInstanceUID(OFString &value,
2233 const signed long pos) const
2234 {
2235 return getStringValueFromElement(StudyInstanceUID, value, pos);
2236 }
2237
2238
getSeriesInstanceUID(OFString & value,const signed long pos) const2239 OFCondition DSRDocument::getSeriesInstanceUID(OFString &value,
2240 const signed long pos) const
2241 {
2242 return getStringValueFromElement(SeriesInstanceUID, value, pos);
2243 }
2244
2245
getSOPInstanceUID(OFString & value,const signed long pos) const2246 OFCondition DSRDocument::getSOPInstanceUID(OFString &value,
2247 const signed long pos) const
2248 {
2249 return getStringValueFromElement(SOPInstanceUID, value, pos);
2250 }
2251
2252
getInstanceCreatorUID(OFString & value,const signed long pos) const2253 OFCondition DSRDocument::getInstanceCreatorUID(OFString &value,
2254 const signed long pos) const
2255 {
2256 return getStringValueFromElement(InstanceCreatorUID, value, pos);
2257 }
2258
2259
getTimezoneOffsetFromUTC(OFString & value,const signed long pos) const2260 OFCondition DSRDocument::getTimezoneOffsetFromUTC(OFString &value,
2261 const signed long pos) const
2262 {
2263 return getStringValueFromElement(TimezoneOffsetFromUTC, value, pos);
2264 }
2265
2266
getPatientName(OFString & value,const signed long pos) const2267 OFCondition DSRDocument::getPatientName(OFString &value,
2268 const signed long pos) const
2269 {
2270 return getStringValueFromElement(PatientName, value, pos);
2271 }
2272
2273
getPatientBirthDate(OFString & value,const signed long pos) const2274 OFCondition DSRDocument::getPatientBirthDate(OFString &value,
2275 const signed long pos) const
2276 {
2277 return getStringValueFromElement(PatientBirthDate, value, pos);
2278 }
2279
2280
getPatientSex(OFString & value,const signed long pos) const2281 OFCondition DSRDocument::getPatientSex(OFString &value,
2282 const signed long pos) const
2283 {
2284 return getStringValueFromElement(PatientSex, value, pos);
2285 }
2286
2287
getReferringPhysicianName(OFString & value,const signed long pos) const2288 OFCondition DSRDocument::getReferringPhysicianName(OFString &value,
2289 const signed long pos) const
2290 {
2291 return getStringValueFromElement(ReferringPhysicianName, value, pos);
2292 }
2293
2294
getStudyDescription(OFString & value,const signed long pos) const2295 OFCondition DSRDocument::getStudyDescription(OFString &value,
2296 const signed long pos) const
2297 {
2298 return getStringValueFromElement(StudyDescription, value, pos);
2299 }
2300
2301
getSeriesDescription(OFString & value,const signed long pos) const2302 OFCondition DSRDocument::getSeriesDescription(OFString &value,
2303 const signed long pos) const
2304 {
2305 return getStringValueFromElement(SeriesDescription, value, pos);
2306 }
2307
2308
getProtocolName(OFString & value,const signed long pos) const2309 OFCondition DSRDocument::getProtocolName(OFString &value,
2310 const signed long pos) const
2311 {
2312 return getStringValueFromElement(ProtocolName, value, pos);
2313 }
2314
2315
getManufacturer(OFString & value,const signed long pos) const2316 OFCondition DSRDocument::getManufacturer(OFString &value,
2317 const signed long pos) const
2318 {
2319 return getStringValueFromElement(Manufacturer, value, pos);
2320 }
2321
2322
getManufacturerModelName(OFString & value,const signed long pos) const2323 OFCondition DSRDocument::getManufacturerModelName(OFString &value,
2324 const signed long pos) const
2325 {
2326 return getStringValueFromElement(ManufacturerModelName, value, pos);
2327 }
2328
2329
getDeviceSerialNumber(OFString & value,const signed long pos) const2330 OFCondition DSRDocument::getDeviceSerialNumber(OFString &value,
2331 const signed long pos) const
2332 {
2333 return getStringValueFromElement(DeviceSerialNumber, value, pos);
2334 }
2335
2336
getSoftwareVersions(OFString & value,const signed long pos) const2337 OFCondition DSRDocument::getSoftwareVersions(OFString &value,
2338 const signed long pos) const
2339 {
2340 return getStringValueFromElement(SoftwareVersions, value, pos);
2341 }
2342
2343
getSynchronizationFrameOfReferenceUID(OFString & value,const signed long pos) const2344 OFCondition DSRDocument::getSynchronizationFrameOfReferenceUID(OFString &value,
2345 const signed long pos) const
2346 {
2347 return getStringValueFromElement(SynchronizationFrameOfReferenceUID, value, pos);
2348 }
2349
2350
getSynchronizationTrigger(OFString & value,const signed long pos) const2351 OFCondition DSRDocument::getSynchronizationTrigger(OFString &value,
2352 const signed long pos) const
2353 {
2354 return getStringValueFromElement(SynchronizationTrigger, value, pos);
2355 }
2356
2357
getAcquisitionTimeSynchronized(OFString & value,const signed long pos) const2358 OFCondition DSRDocument::getAcquisitionTimeSynchronized(OFString &value,
2359 const signed long pos) const
2360 {
2361 return getStringValueFromElement(AcquisitionTimeSynchronized, value, pos);
2362 }
2363
2364
getStudyDate(OFString & value,const signed long pos) const2365 OFCondition DSRDocument::getStudyDate(OFString &value,
2366 const signed long pos) const
2367 {
2368 return getStringValueFromElement(StudyDate, value, pos);
2369 }
2370
2371
getStudyTime(OFString & value,const signed long pos) const2372 OFCondition DSRDocument::getStudyTime(OFString &value,
2373 const signed long pos) const
2374 {
2375 return getStringValueFromElement(StudyTime, value, pos);
2376 }
2377
2378
getSeriesDate(OFString & value,const signed long pos) const2379 OFCondition DSRDocument::getSeriesDate(OFString &value,
2380 const signed long pos) const
2381 {
2382 return getStringValueFromElement(SeriesDate, value, pos);
2383 }
2384
2385
getSeriesTime(OFString & value,const signed long pos) const2386 OFCondition DSRDocument::getSeriesTime(OFString &value,
2387 const signed long pos) const
2388 {
2389 return getStringValueFromElement(SeriesTime, value, pos);
2390 }
2391
2392
getInstanceCreationDate(OFString & value,const signed long pos) const2393 OFCondition DSRDocument::getInstanceCreationDate(OFString &value,
2394 const signed long pos) const
2395 {
2396 return getStringValueFromElement(InstanceCreationDate, value, pos);
2397 }
2398
2399
getInstanceCreationTime(OFString & value,const signed long pos) const2400 OFCondition DSRDocument::getInstanceCreationTime(OFString &value,
2401 const signed long pos) const
2402 {
2403 return getStringValueFromElement(InstanceCreationTime, value, pos);
2404 }
2405
2406
getContentDate(OFString & value,const signed long pos) const2407 OFCondition DSRDocument::getContentDate(OFString &value,
2408 const signed long pos) const
2409 {
2410 return getStringValueFromElement(ContentDate, value, pos);
2411 }
2412
2413
getContentTime(OFString & value,const signed long pos) const2414 OFCondition DSRDocument::getContentTime(OFString &value,
2415 const signed long pos) const
2416 {
2417 return getStringValueFromElement(ContentTime, value, pos);
2418 }
2419
2420
getStudyID(OFString & value,const signed long pos) const2421 OFCondition DSRDocument::getStudyID(OFString &value,
2422 const signed long pos) const
2423 {
2424 return getStringValueFromElement(StudyID, value, pos);
2425 }
2426
2427
getPatientID(OFString & value,const signed long pos) const2428 OFCondition DSRDocument::getPatientID(OFString &value,
2429 const signed long pos) const
2430 {
2431 return getStringValueFromElement(PatientID, value, pos);
2432 }
2433
2434
getIssuerOfPatientID(OFString & value,const signed long pos) const2435 OFCondition DSRDocument::getIssuerOfPatientID(OFString &value,
2436 const signed long pos) const
2437 {
2438 return getStringValueFromElement(IssuerOfPatientID, value, pos);
2439 }
2440
2441
getSeriesNumber(OFString & value,const signed long pos) const2442 OFCondition DSRDocument::getSeriesNumber(OFString &value,
2443 const signed long pos) const
2444 {
2445 return getStringValueFromElement(SeriesNumber, value, pos);
2446 }
2447
2448
getInstanceNumber(OFString & value,const signed long pos) const2449 OFCondition DSRDocument::getInstanceNumber(OFString &value,
2450 const signed long pos) const
2451 {
2452 return getStringValueFromElement(InstanceNumber, value, pos);
2453 }
2454
2455
getAccessionNumber(OFString & value,const signed long pos) const2456 OFCondition DSRDocument::getAccessionNumber(OFString &value,
2457 const signed long pos) const
2458 {
2459 return getStringValueFromElement(AccessionNumber, value, pos);
2460 }
2461
2462
2463 // --- set attributes ---
2464
setSpecificCharacterSet(const OFString & value,const OFBool check)2465 OFCondition DSRDocument::setSpecificCharacterSet(const OFString &value,
2466 const OFBool check)
2467 {
2468 OFCondition result = (check) ? DcmCodeString::checkStringValue(value, "1-n") : EC_Normal;
2469 if (result.good())
2470 {
2471 /* try to map the given string to an enum value */
2472 SpecificCharacterSetEnum = definedTermToCharacterSet(value);
2473 if (SpecificCharacterSetEnum == CS_unknown)
2474 DCMSR_WARN("Setting unknown/unsupported value for Specific Character Set: " << value);
2475 /* in any case, store the passed string value */
2476 result = SpecificCharacterSet.putOFStringArray(value);
2477 }
2478 return result;
2479 }
2480
2481
setCompletionFlagDescription(const OFString & value,const OFBool check)2482 OFCondition DSRDocument::setCompletionFlagDescription(const OFString &value,
2483 const OFBool check)
2484 {
2485 OFCondition result = EC_IllegalCall;
2486 /* not all SR IODs contain the SR Document General Module */
2487 if (usesSRDocumentGeneralModule(getDocumentType()))
2488 {
2489 if (check)
2490 result = DcmLongString::checkStringValue(value, "1", getSpecificCharacterSet());
2491 if (result.good())
2492 result = CompletionFlagDescription.putOFStringArray(value);
2493 }
2494 return result;
2495 }
2496
2497
setTimezoneOffsetFromUTC(const OFString & value,const OFBool check)2498 OFCondition DSRDocument::setTimezoneOffsetFromUTC(const OFString &value,
2499 const OFBool check)
2500 {
2501 OFCondition result = (check) ? DcmShortString::checkStringValue(value, "1", getSpecificCharacterSet()) : EC_Normal;
2502 if (result.good())
2503 result = TimezoneOffsetFromUTC.putOFStringArray(value);
2504 return result;
2505 }
2506
2507
setPatientName(const OFString & value,const OFBool check)2508 OFCondition DSRDocument::setPatientName(const OFString &value,
2509 const OFBool check)
2510 {
2511 OFCondition result = (check) ? DcmPersonName::checkStringValue(value, "1", getSpecificCharacterSet()) : EC_Normal;
2512 if (result.good())
2513 result = PatientName.putOFStringArray(value);
2514 return result;
2515 }
2516
2517
setPatientBirthDate(const OFString & value,const OFBool check)2518 OFCondition DSRDocument::setPatientBirthDate(const OFString &value,
2519 const OFBool check)
2520 {
2521 OFCondition result = (check) ? DcmDate::checkStringValue(value, "1") : EC_Normal;
2522 if (result.good())
2523 result = PatientBirthDate.putOFStringArray(value);
2524 return result;
2525 }
2526
2527
setPatientSex(const OFString & value,const OFBool check)2528 OFCondition DSRDocument::setPatientSex(const OFString &value,
2529 const OFBool check)
2530 {
2531 OFCondition result = (check) ? DcmCodeString::checkStringValue(value, "1") : EC_Normal;
2532 if (result.good())
2533 result = PatientSex.putOFStringArray(value);
2534 return result;
2535 }
2536
2537
setReferringPhysicianName(const OFString & value,const OFBool check)2538 OFCondition DSRDocument::setReferringPhysicianName(const OFString &value,
2539 const OFBool check)
2540 {
2541 OFCondition result = (check) ? DcmPersonName::checkStringValue(value, "1", getSpecificCharacterSet()) : EC_Normal;
2542 if (result.good())
2543 result = ReferringPhysicianName.putOFStringArray(value);
2544 return result;
2545 }
2546
2547
setStudyDescription(const OFString & value,const OFBool check)2548 OFCondition DSRDocument::setStudyDescription(const OFString &value,
2549 const OFBool check)
2550 {
2551 OFCondition result = (check) ? DcmLongString::checkStringValue(value, "1", getSpecificCharacterSet()) : EC_Normal;
2552 if (result.good())
2553 result = StudyDescription.putOFStringArray(value);
2554 return result;
2555 }
2556
2557
setSeriesDescription(const OFString & value,const OFBool check)2558 OFCondition DSRDocument::setSeriesDescription(const OFString &value,
2559 const OFBool check)
2560 {
2561 OFCondition result = (check) ? DcmLongString::checkStringValue(value, "1", getSpecificCharacterSet()) : EC_Normal;
2562 if (result.good())
2563 result = SeriesDescription.putOFStringArray(value);
2564 return result;
2565 }
2566
2567
setProtocolName(const OFString & value,const OFBool check)2568 OFCondition DSRDocument::setProtocolName(const OFString &value,
2569 const OFBool check)
2570 {
2571 OFCondition result = (check) ? DcmLongString::checkStringValue(value, "1", getSpecificCharacterSet()) : EC_Normal;
2572 if (result.good())
2573 result = ProtocolName.putOFStringArray(value);
2574 return result;
2575 }
2576
2577
setManufacturer(const OFString & value,const OFBool check)2578 OFCondition DSRDocument::setManufacturer(const OFString &value,
2579 const OFBool check)
2580 {
2581 OFCondition result = (check) ? DcmLongString::checkStringValue(value, "1", getSpecificCharacterSet()) : EC_Normal;
2582 if (result.good())
2583 result = Manufacturer.putOFStringArray(value);
2584 return result;
2585 }
2586
2587
setManufacturerModelName(const OFString & value,const OFBool check)2588 OFCondition DSRDocument::setManufacturerModelName(const OFString &value,
2589 const OFBool check)
2590 {
2591 OFCondition result = (check) ? DcmLongString::checkStringValue(value, "1", getSpecificCharacterSet()) : EC_Normal;
2592 if (result.good())
2593 result = ManufacturerModelName.putOFStringArray(value);
2594 return result;
2595 }
2596
2597
setDeviceSerialNumber(const OFString & value,const OFBool check)2598 OFCondition DSRDocument::setDeviceSerialNumber(const OFString &value,
2599 const OFBool check)
2600 {
2601 OFCondition result = (check) ? DcmLongString::checkStringValue(value, "1", getSpecificCharacterSet()) : EC_Normal;
2602 if (result.good())
2603 result = DeviceSerialNumber.putOFStringArray(value);
2604 return result;
2605 }
2606
2607
setSoftwareVersions(const OFString & value,const OFBool check)2608 OFCondition DSRDocument::setSoftwareVersions(const OFString &value,
2609 const OFBool check)
2610 {
2611 OFCondition result = (check) ? DcmLongString::checkStringValue(value, "1-n", getSpecificCharacterSet()) : EC_Normal;
2612 if (result.good())
2613 result = SoftwareVersions.putOFStringArray(value);
2614 return result;
2615 }
2616
2617
setSynchronizationFrameOfReferenceUID(const OFString & value,const OFBool check)2618 OFCondition DSRDocument::setSynchronizationFrameOfReferenceUID(const OFString &value,
2619 const OFBool check)
2620 {
2621 OFCondition result = (check) ? DcmUniqueIdentifier::checkStringValue(value, "1") : EC_Normal;
2622 if (result.good())
2623 result = SynchronizationFrameOfReferenceUID.putOFStringArray(value);
2624 return result;
2625 }
2626
2627
setSynchronizationTrigger(const OFString & value,const OFBool check)2628 OFCondition DSRDocument::setSynchronizationTrigger(const OFString &value,
2629 const OFBool check)
2630 {
2631 OFCondition result = (check) ? DcmCodeString::checkStringValue(value, "1") : EC_Normal;
2632 if (result.good())
2633 result = SynchronizationTrigger.putOFStringArray(value);
2634 return result;
2635 }
2636
2637
setAcquisitionTimeSynchronized(const OFString & value,const OFBool check)2638 OFCondition DSRDocument::setAcquisitionTimeSynchronized(const OFString &value,
2639 const OFBool check)
2640 {
2641 OFCondition result = (check) ? DcmCodeString::checkStringValue(value, "1") : EC_Normal;
2642 if (result.good())
2643 result = AcquisitionTimeSynchronized.putOFStringArray(value);
2644 return result;
2645 }
2646
2647
setContentDate(const OFString & value,const OFBool check)2648 OFCondition DSRDocument::setContentDate(const OFString &value,
2649 const OFBool check)
2650 {
2651 OFCondition result = (check) ? DcmDate::checkStringValue(value, "1") : EC_Normal;
2652 if (result.good())
2653 result = ContentDate.putOFStringArray(value);
2654 return result;
2655 }
2656
2657
setContentTime(const OFString & value,const OFBool check)2658 OFCondition DSRDocument::setContentTime(const OFString &value,
2659 const OFBool check)
2660 {
2661 OFCondition result = (check) ? DcmTime::checkStringValue(value, "1") : EC_Normal;
2662 if (result.good())
2663 result = ContentTime.putOFStringArray(value);
2664 return result;
2665 }
2666
2667
setStudyDate(const OFString & value,const OFBool check)2668 OFCondition DSRDocument::setStudyDate(const OFString &value,
2669 const OFBool check)
2670 {
2671 OFCondition result = (check) ? DcmDate::checkStringValue(value, "1") : EC_Normal;
2672 if (result.good())
2673 result = StudyDate.putOFStringArray(value);
2674 return result;
2675 }
2676
2677
setStudyTime(const OFString & value,const OFBool check)2678 OFCondition DSRDocument::setStudyTime(const OFString &value,
2679 const OFBool check)
2680 {
2681 OFCondition result = (check) ? DcmTime::checkStringValue(value, "1") : EC_Normal;
2682 if (result.good())
2683 result = StudyTime.putOFStringArray(value);
2684 return result;
2685 }
2686
2687
setSeriesDate(const OFString & value,const OFBool check)2688 OFCondition DSRDocument::setSeriesDate(const OFString &value,
2689 const OFBool check)
2690 {
2691 OFCondition result = (check) ? DcmDate::checkStringValue(value, "1") : EC_Normal;
2692 if (result.good())
2693 result = SeriesDate.putOFStringArray(value);
2694 return result;
2695 }
2696
2697
setSeriesTime(const OFString & value,const OFBool check)2698 OFCondition DSRDocument::setSeriesTime(const OFString &value,
2699 const OFBool check)
2700 {
2701 OFCondition result = (check) ? DcmTime::checkStringValue(value, "1") : EC_Normal;
2702 if (result.good())
2703 result = SeriesTime.putOFStringArray(value);
2704 return result;
2705 }
2706
2707
setStudyID(const OFString & value,const OFBool check)2708 OFCondition DSRDocument::setStudyID(const OFString &value,
2709 const OFBool check)
2710 {
2711 OFCondition result = (check) ? DcmShortString::checkStringValue(value, "1", getSpecificCharacterSet()) : EC_Normal;
2712 if (result.good())
2713 result = StudyID.putOFStringArray(value);
2714 return result;
2715 }
2716
2717
setPatientID(const OFString & value,const OFBool check)2718 OFCondition DSRDocument::setPatientID(const OFString &value,
2719 const OFBool check)
2720 {
2721 OFCondition result = (check) ? DcmLongString::checkStringValue(value, "1", getSpecificCharacterSet()) : EC_Normal;
2722 if (result.good())
2723 result = PatientID.putOFStringArray(value);
2724 return result;
2725 }
2726
2727
setIssuerOfPatientID(const OFString & value,const OFBool check)2728 OFCondition DSRDocument::setIssuerOfPatientID(const OFString &value,
2729 const OFBool check)
2730 {
2731 OFCondition result = (check) ? DcmLongString::checkStringValue(value, "1", getSpecificCharacterSet()) : EC_Normal;
2732 if (result.good())
2733 result = IssuerOfPatientID.putOFStringArray(value);
2734 return result;
2735 }
2736
2737
setSeriesNumber(const OFString & value,const OFBool check)2738 OFCondition DSRDocument::setSeriesNumber(const OFString &value,
2739 const OFBool check)
2740 {
2741 OFCondition result = (check) ? DcmIntegerString::checkStringValue(value, "1") : EC_Normal;
2742 if (result.good())
2743 result = SeriesNumber.putOFStringArray(value);
2744 return result;
2745 }
2746
2747
setInstanceNumber(const OFString & value,const OFBool check)2748 OFCondition DSRDocument::setInstanceNumber(const OFString &value,
2749 const OFBool check)
2750 {
2751 OFCondition result = (check) ? DcmIntegerString::checkStringValue(value, "1") : EC_Normal;
2752 if (result.good())
2753 result = InstanceNumber.putOFStringArray(value);
2754 return result;
2755 }
2756
2757
setAccessionNumber(const OFString & value,const OFBool check)2758 OFCondition DSRDocument::setAccessionNumber(const OFString &value,
2759 const OFBool check)
2760 {
2761 OFCondition result = (check) ? DcmShortString::checkStringValue(value, "1", getSpecificCharacterSet()) : EC_Normal;
2762 if (result.good())
2763 result = AccessionNumber.putOFStringArray(value);
2764 return result;
2765 }
2766
2767
2768 // --- document management functions
2769
createNewStudy()2770 void DSRDocument::createNewStudy()
2771 {
2772 /* clear all study-related attributes */
2773 StudyInstanceUID.clear();
2774 StudyDate.clear();
2775 StudyTime.clear();
2776 ReferringPhysicianName.clear();
2777 StudyID.clear();
2778 AccessionNumber.clear();
2779 StudyDescription.clear();
2780 /* also creates new study (since UID is empty) and SOP instance */
2781 createNewSeries();
2782 }
2783
2784
createNewSeries()2785 void DSRDocument::createNewSeries()
2786 {
2787 /* clear all series-related attributes */
2788 SeriesInstanceUID.clear();
2789 SeriesNumber.clear();
2790 SeriesDate.clear();
2791 SeriesTime.clear();
2792 SeriesDescription.clear();
2793 /* also creates new series (since UID is empty) */
2794 createNewSOPInstance();
2795 }
2796
2797
createNewSeriesInStudy(const OFString & studyUID,const OFBool check)2798 OFCondition DSRDocument::createNewSeriesInStudy(const OFString &studyUID,
2799 const OFBool check)
2800 {
2801 OFCondition result = EC_IllegalParameter;
2802 if (!studyUID.empty())
2803 {
2804 /* check parameter for conformance with VR and VM (if needed) */
2805 result = (check) ? DcmUniqueIdentifier::checkStringValue(studyUID, "1") : EC_Normal;
2806 if (result.good())
2807 {
2808 StudyInstanceUID.putOFStringArray(studyUID);
2809 /* also creates new SOP instance */
2810 createNewSeries();
2811 }
2812 }
2813 return result;
2814 }
2815
2816
createNewSOPInstance()2817 void DSRDocument::createNewSOPInstance()
2818 {
2819 SOPInstanceUID.clear();
2820 /* reset FinalizedFlag */
2821 FinalizedFlag = OFFalse;
2822 /* update all DICOM attributes (incl. empty UIDs) */
2823 updateAttributes();
2824 }
2825
2826
createNewDocument()2827 OFCondition DSRDocument::createNewDocument()
2828 {
2829 /* create new document with the same type as the current one */
2830 return createNewDocument(getDocumentType());
2831 }
2832
2833
createNewDocument(const E_DocumentType documentType)2834 OFCondition DSRDocument::createNewDocument(const E_DocumentType documentType)
2835 {
2836 /* document type is stored only once (namely in the document tree) */
2837 OFCondition result = DocumentTree.changeDocumentType(documentType, OFTrue /*deleteTree*/);
2838 if (result.good())
2839 {
2840 /* clear object (all member variables) */
2841 clear();
2842 /* set initial values for a new SOP instance */
2843 createNewSOPInstance();
2844 }
2845 return result;
2846 }
2847
2848
changeDocumentType(const E_DocumentType documentType)2849 OFCondition DSRDocument::changeDocumentType(const E_DocumentType documentType)
2850 {
2851 /* document type is stored only once (namely in the document tree) */
2852 OFCondition result = DocumentTree.changeDocumentType(documentType, OFFalse /*deleteTree*/);
2853 if (result.good())
2854 {
2855 /* update IOD-specific DICOM attributes */
2856 updateAttributes(OFFalse /*updateAll*/);
2857 }
2858 return result;
2859 }
2860
2861
createRevisedVersion(const OFBool clearList)2862 OFCondition DSRDocument::createRevisedVersion(const OFBool clearList)
2863 {
2864 OFCondition result = EC_IllegalCall;
2865 /* not all SR IODs contain the SR Document General Module */
2866 if (usesSRDocumentGeneralModule(getDocumentType()))
2867 {
2868 /* check whether document is already completed */
2869 if (CompletionFlagEnum == CF_Complete)
2870 {
2871 if (clearList)
2872 PredecessorDocuments.clear();
2873 /* add current document */
2874 OFString studyUID, seriesUID, classUID, instanceUID;
2875 result = PredecessorDocuments.addItem(getStringValueFromElement(StudyInstanceUID, studyUID),
2876 getStringValueFromElement(SeriesInstanceUID, seriesUID),
2877 getStringValueFromElement(SOPClassUID, classUID),
2878 getStringValueFromElement(SOPInstanceUID, instanceUID));
2879 if (result.good())
2880 {
2881 IdenticalDocuments.clear();
2882 /* reset completion flag, delete description */
2883 CompletionFlagEnum = CF_invalid;
2884 CompletionFlagDescription.clear();
2885 /* clear content date/time, will be set automatically in updateAttributes() */
2886 ContentDate.clear();
2887 ContentTime.clear();
2888 /* clear list of verifying observers and set flag to UNVERIFIED */
2889 removeVerification();
2890 /* remove digital signatures from document tree */
2891 DocumentTree.removeSignatures();
2892 /* create new instance UID, update creation date/time and reset finalized flag */
2893 createNewSOPInstance();
2894 }
2895 }
2896 }
2897 return result;
2898 }
2899
2900
completeDocument()2901 OFCondition DSRDocument::completeDocument()
2902 {
2903 /* complete document with empty/absent completion description */
2904 return completeDocument("");
2905 }
2906
2907
completeDocument(const OFString & description,const OFBool check)2908 OFCondition DSRDocument::completeDocument(const OFString &description,
2909 const OFBool check)
2910 {
2911 OFCondition result = EC_IllegalCall;
2912 /* not all SR IODs contain the SR Document General Module */
2913 if (usesSRDocumentGeneralModule(getDocumentType()))
2914 {
2915 /* if document is not already completed */
2916 if (CompletionFlagEnum != CF_Complete)
2917 {
2918 /* check parameter for conformance with VR and VM (if needed) */
2919 result = (check) ? DcmLongString::checkStringValue(description, "1", getSpecificCharacterSet()) : EC_Normal;
2920 if (result.good())
2921 {
2922 /* completed for now and ever */
2923 CompletionFlagEnum = CF_Complete;
2924 /* completion flag description */
2925 setCompletionFlagDescription(description);
2926 }
2927 }
2928 }
2929 return result;
2930 }
2931
2932
verifyDocument(const OFString & observerName,const OFString & organization,const OFString & dateTime,const OFBool check)2933 OFCondition DSRDocument::verifyDocument(const OFString &observerName,
2934 const OFString &organization,
2935 const OFString &dateTime,
2936 const OFBool check)
2937 {
2938 /* empty CodedEntryValue */
2939 return verifyDocument(observerName, DSRCodedEntryValue() /*dummy*/, organization, dateTime, check);
2940 }
2941
2942
verifyDocument(const OFString & observerName,const DSRCodedEntryValue & observerCode,const OFString & organization,const OFString & dateTime,const OFBool check)2943 OFCondition DSRDocument::verifyDocument(const OFString &observerName,
2944 const DSRCodedEntryValue &observerCode,
2945 const OFString &organization,
2946 const OFString &dateTime,
2947 const OFBool check)
2948 {
2949 OFCondition result = EC_IllegalCall;
2950 /* not all SR IODs contain the SR Document General Module */
2951 if (usesSRDocumentGeneralModule(getDocumentType()))
2952 {
2953 /* verify completed documents only */
2954 if (CompletionFlagEnum == CF_Complete)
2955 {
2956 /* empty strings are not allowed (type 1 attributes) */
2957 if (!observerName.empty() && !organization.empty())
2958 {
2959 /* check parameters for conformance with VR and VM (if needed) */
2960 if (check)
2961 {
2962 if (observerCode.isEmpty() || observerCode.isValid())
2963 result = EC_Normal;
2964 if (result.good())
2965 result = DcmPersonName::checkStringValue(observerName, "1", getSpecificCharacterSet());
2966 if (result.good())
2967 result = DcmLongString::checkStringValue(organization, "1", getSpecificCharacterSet());
2968 if (result.good())
2969 result = DcmDateTime::checkStringValue(dateTime, "1");
2970 } else {
2971 /* no checks, so everything is fine */
2972 result = EC_Normal;
2973 }
2974 if (result.good())
2975 {
2976 DcmItem *ditem = new DcmItem();
2977 if (ditem != NULL)
2978 {
2979 /* write VerifyingObserverName */
2980 putStringValueToDataset(*ditem, DCM_VerifyingObserverName, observerName);
2981 /* write VerifyingObserverIdentificationCodeSequence (might be empty, type 2) */
2982 observerCode.writeSequence(*ditem, DCM_VerifyingObserverIdentificationCodeSequence);
2983 /* write VerifyingOrganization */
2984 putStringValueToDataset(*ditem, DCM_VerifyingOrganization, organization);
2985 /* write VerificationDateTime */
2986 if (dateTime.empty())
2987 {
2988 OFString tmpString;
2989 currentDateTime(tmpString);
2990 putStringValueToDataset(*ditem, DCM_VerificationDateTime, tmpString);
2991 } else
2992 putStringValueToDataset(*ditem, DCM_VerificationDateTime, dateTime);
2993 /* insert items into sequence */
2994 VerifyingObserver.insert(ditem);
2995 /* set VerificationFlag to VERIFIED */
2996 VerificationFlagEnum = VF_Verified;
2997 /* reset FinalizedFlag */
2998 FinalizedFlag = OFFalse;
2999 } else
3000 result = EC_MemoryExhausted;
3001 }
3002 } else
3003 result = EC_IllegalParameter;
3004 }
3005 }
3006 return result;
3007 }
3008
3009
removeVerification()3010 void DSRDocument::removeVerification()
3011 {
3012 /* clear list of verifying observers and set flag to UNVERIFIED */
3013 VerifyingObserver.clear();
3014 VerificationFlagEnum = VF_Unverified;
3015 /* reset FinalizedFlag */
3016 FinalizedFlag = OFFalse;
3017 }
3018
3019
finalizeDocument()3020 OFCondition DSRDocument::finalizeDocument()
3021 {
3022 OFCondition result = EC_IllegalCall;
3023 /* document can only be finalized if it is already completed */
3024 if (CompletionFlagEnum == CF_Complete)
3025 {
3026 /* set FinalizedFlag */
3027 FinalizedFlag = OFTrue;
3028 result = EC_Normal;
3029 }
3030 return result;
3031 }
3032
3033
updateAttributes(const OFBool updateAll,const OFBool verboseMode)3034 void DSRDocument::updateAttributes(const OFBool updateAll,
3035 const OFBool verboseMode)
3036 {
3037 if (verboseMode)
3038 DCMSR_DEBUG("Updating " << (updateAll ? "all " : "") << "DICOM header attributes");
3039 const E_DocumentType documentType = getDocumentType();
3040 /* retrieve SOP class UID from internal document type */
3041 SOPClassUID.putString(documentTypeToSOPClassUID(documentType));
3042 /* put modality string depending on document type */
3043 Modality.putString(documentTypeToModality(documentType));
3044 if (updateAll)
3045 {
3046 OFString tmpString;
3047 /* determine local timezone (if required) */
3048 if (requiresTimezoneModule(documentType) && TimezoneOffsetFromUTC.isEmpty())
3049 {
3050 DCMSR_DEBUG(" Determining local timezone for Timezone Offset From UTC");
3051 TimezoneOffsetFromUTC.putOFStringArray(localTimezone(tmpString));
3052 }
3053
3054 /* create new instance number if required (type 1) */
3055 if (InstanceNumber.isEmpty())
3056 InstanceNumber.putString("1");
3057 /* create new series number if required (type 1) */
3058 if (SeriesNumber.isEmpty())
3059 SeriesNumber.putString("1");
3060
3061 char uid[100];
3062 /* create new SOP instance UID if required */
3063 if (SOPInstanceUID.isEmpty())
3064 {
3065 if (verboseMode)
3066 DCMSR_DEBUG(" Generating new value for SOP Instance UID");
3067 SOPInstanceUID.putString(dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT));
3068 /* set instance creation date to current date (YYYYMMDD) */
3069 InstanceCreationDate.putOFStringArray(currentDate(tmpString));
3070 /* set instance creation time to current time (HHMMSS) */
3071 InstanceCreationTime.putOFStringArray(currentTime(tmpString));
3072 /* set instance creator UID to identify instances that have been created by this toolkit */
3073 InstanceCreatorUID.putString(OFFIS_INSTANCE_CREATOR_UID);
3074 }
3075 /* create new study instance UID if required */
3076 if (StudyInstanceUID.isEmpty())
3077 {
3078 if (verboseMode)
3079 DCMSR_DEBUG(" Generating new value for Study Instance UID");
3080 StudyInstanceUID.putString(dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT));
3081 }
3082 /* create new series instance UID if required */
3083 if (SeriesInstanceUID.isEmpty())
3084 {
3085 if (verboseMode)
3086 DCMSR_DEBUG(" Generating new value for Series Instance UID");
3087 SeriesInstanceUID.putString(dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT));
3088 }
3089
3090 /* check and set content date if required */
3091 if (ContentDate.isEmpty())
3092 ContentDate.putString(getStringValueFromElement(InstanceCreationDate));
3093 /* check and set content time if required */
3094 if (ContentTime.isEmpty())
3095 ContentTime.putString(getStringValueFromElement(InstanceCreationTime));
3096 }
3097 /* not all SR IODs contain the SR Document General Module */
3098 if (usesSRDocumentGeneralModule(documentType))
3099 {
3100 /* set preliminary flag */
3101 PreliminaryFlag.putString(preliminaryFlagToEnumeratedValue(PreliminaryFlagEnum));
3102 /* check and adjust completion flag if required */
3103 if (CompletionFlagEnum == CF_invalid)
3104 CompletionFlagEnum = CF_Partial;
3105 CompletionFlag.putString(completionFlagToEnumeratedValue(CompletionFlagEnum));
3106 /* check and adjust verification flag if required */
3107 if (VerificationFlagEnum == VF_invalid)
3108 VerificationFlagEnum = VF_Unverified;
3109 VerificationFlag.putString(verificationFlagToEnumeratedValue(VerificationFlagEnum));
3110 } else {
3111 /* if not, reset the various flags and clear related information */
3112 PreliminaryFlagEnum = PF_invalid;
3113 CompletionFlagEnum = CF_invalid;
3114 VerificationFlagEnum = VF_invalid;
3115 PreliminaryFlag.clear();
3116 CompletionFlag.clear();
3117 CompletionFlagDescription.clear();
3118 VerificationFlag.clear();
3119 VerifyingObserver.clear();
3120 }
3121 }
3122