/*
*
* Copyright (C) 2000-2022, OFFIS e.V.
* All rights reserved. See COPYRIGHT file for details.
*
* This software and supporting documentation were developed by
*
* OFFIS e.V.
* R&D Division Health
* Escherweg 2
* D-26121 Oldenburg, Germany
*
*
* Module: dcmsr
*
* Author: Joerg Riesmeier
*
* Purpose:
* classes: DSRDocument
*
*/
#include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */
#include "dcmtk/dcmsr/dsrdoc.h"
#include "dcmtk/dcmsr/dsrxmld.h"
#include "dcmtk/dcmsr/dsrpnmtn.h"
#include "dcmtk/dcmsr/dsrdattn.h"
#include "dcmtk/dcmsr/dsrdtitn.h"
#include "dcmtk/dcmsr/dsrtimtn.h"
#include "dcmtk/dcmdata/dcdeftag.h"
#include "dcmtk/dcmdata/dcuid.h"
#include "dcmtk/dcmdata/dcvrdt.h"
/*---------------------*
* macro definitions *
*---------------------*/
#define DCMSR_PRINT_HEADER_FIELD_START(header_name, delimiter) \
DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_HEADER_NAME) \
stream << header_name; \
DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_DELIMITER) \
stream << delimiter; \
DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_HEADER_VALUE)
#define DCMSR_PRINT_HEADER_FIELD_END \
DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_RESET) \
stream << OFendl;
/*------------------*
* implementation *
*------------------*/
DSRDocument::DSRDocument(const E_DocumentType documentType)
: DocumentTree(documentType),
FinalizedFlag(OFFalse),
PreliminaryFlagEnum(PF_invalid),
CompletionFlagEnum(CF_invalid),
VerificationFlagEnum(VF_invalid),
SpecificCharacterSetEnum(CS_default),
SOPClassUID(DCM_SOPClassUID),
SOPInstanceUID(DCM_SOPInstanceUID),
SpecificCharacterSet(DCM_SpecificCharacterSet),
InstanceCreationDate(DCM_InstanceCreationDate),
InstanceCreationTime(DCM_InstanceCreationTime),
InstanceCreatorUID(DCM_InstanceCreatorUID),
CodingSchemeIdentification(),
TimezoneOffsetFromUTC(DCM_TimezoneOffsetFromUTC),
StudyInstanceUID(DCM_StudyInstanceUID),
StudyDate(DCM_StudyDate),
StudyTime(DCM_StudyTime),
ReferringPhysicianName(DCM_ReferringPhysicianName),
StudyID(DCM_StudyID),
AccessionNumber(DCM_AccessionNumber),
StudyDescription(DCM_StudyDescription),
PatientName(DCM_PatientName),
PatientID(DCM_PatientID),
IssuerOfPatientID(DCM_IssuerOfPatientID),
PatientBirthDate(DCM_PatientBirthDate),
PatientSex(DCM_PatientSex),
PatientSize(DCM_PatientSize),
PatientWeight(DCM_PatientWeight),
Manufacturer(DCM_Manufacturer),
ManufacturerModelName(DCM_ManufacturerModelName),
DeviceSerialNumber(DCM_DeviceSerialNumber),
SoftwareVersions(DCM_SoftwareVersions),
SynchronizationFrameOfReferenceUID(DCM_SynchronizationFrameOfReferenceUID),
SynchronizationTrigger(DCM_SynchronizationTrigger),
AcquisitionTimeSynchronized(DCM_AcquisitionTimeSynchronized),
Modality(DCM_Modality),
SeriesInstanceUID(DCM_SeriesInstanceUID),
SeriesNumber(DCM_SeriesNumber),
SeriesDate(DCM_SeriesDate),
SeriesTime(DCM_SeriesTime),
ProtocolName(DCM_ProtocolName),
SeriesDescription(DCM_SeriesDescription),
ReferencedPerformedProcedureStep(DCM_ReferencedPerformedProcedureStepSequence),
InstanceNumber(DCM_InstanceNumber),
PreliminaryFlag(DCM_PreliminaryFlag),
CompletionFlag(DCM_CompletionFlag),
CompletionFlagDescription(DCM_CompletionFlagDescription),
VerificationFlag(DCM_VerificationFlag),
ContentDate(DCM_ContentDate),
ContentTime(DCM_ContentTime),
VerifyingObserver(DCM_VerifyingObserverSequence),
PredecessorDocuments(DCM_PredecessorDocumentsSequence),
IdenticalDocuments(DCM_IdenticalDocumentsSequence),
PerformedProcedureCode(DCM_PerformedProcedureCodeSequence),
CurrentRequestedProcedureEvidence(DCM_CurrentRequestedProcedureEvidenceSequence),
PertinentOtherEvidence(DCM_PertinentOtherEvidenceSequence),
ReferencedInstances()
{
DCMSR_DEBUG("Initializing all DICOM header attributes");
/* set initial values for a new SOP instance */
updateAttributes(OFTrue /*updateAll*/, OFFalse /*verboseMode*/);
}
DSRDocument::~DSRDocument()
{
}
void DSRDocument::clear()
{
/* clear SR document tree */
DocumentTree.clear();
FinalizedFlag = OFFalse;
/* clear enumerated values */
PreliminaryFlagEnum = PF_invalid;
CompletionFlagEnum = CF_invalid;
VerificationFlagEnum = VF_invalid;
SpecificCharacterSetEnum = CS_default;
/* clear all DICOM attributes */
SOPClassUID.clear();
SOPInstanceUID.clear();
SpecificCharacterSet.clear();
InstanceCreationDate.clear();
InstanceCreationTime.clear();
InstanceCreatorUID.clear();
CodingSchemeIdentification.clear();
TimezoneOffsetFromUTC.clear();
StudyInstanceUID.clear();
StudyDate.clear();
StudyTime.clear();
ReferringPhysicianName.clear();
StudyID.clear();
AccessionNumber.clear();
StudyDescription.clear();
PatientName.clear();
PatientID.clear();
IssuerOfPatientID.clear();
PatientBirthDate.clear();
PatientSex.clear();
PatientSize.clear();
PatientWeight.clear();
Manufacturer.clear();
ManufacturerModelName.clear();
DeviceSerialNumber.clear();
SoftwareVersions.clear();
SynchronizationFrameOfReferenceUID.clear();
SynchronizationTrigger.clear();
AcquisitionTimeSynchronized.clear();
Modality.clear();
SeriesInstanceUID.clear();
SeriesNumber.clear();
SeriesDate.clear();
SeriesTime.clear();
ProtocolName.clear();
SeriesDescription.clear();
ReferencedPerformedProcedureStep.clear();
InstanceNumber.clear();
PreliminaryFlag.clear();
CompletionFlag.clear();
CompletionFlagDescription.clear();
VerificationFlag.clear();
ContentDate.clear();
ContentTime.clear();
VerifyingObserver.clear();
PerformedProcedureCode.clear();
/* clear list structures */
PredecessorDocuments.clear();
IdenticalDocuments.clear();
CurrentRequestedProcedureEvidence.clear();
PertinentOtherEvidence.clear();
ReferencedInstances.clear();
}
OFBool DSRDocument::isValid()
{
/* document is valid if the document tree is valid and ... */
return DocumentTree.isValid() && !SOPClassUID.isEmpty() && !SOPInstanceUID.isEmpty();
}
OFBool DSRDocument::isFinalized() const
{
return FinalizedFlag;
}
OFCondition DSRDocument::print(STD_NAMESPACE ostream &stream,
const size_t flags)
{
OFCondition result = SR_EC_InvalidDocument;
if (isValid())
{
OFString tmpString, string2;
/* update only some DICOM attributes */
updateAttributes(OFFalse /*updateAll*/);
/* check whether general SR modules are used */
const OFBool usesGeneralSRModules = usesSRDocumentGeneralModule(getDocumentType());
// --- print some general document information ---
if (!(flags & PF_printNoDocumentHeader))
{
/* document type/title */
DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_DOCUMENT_TYPE)
stream << documentTypeToDocumentTitle(getDocumentType(), tmpString);
DCMSR_PRINT_ANSI_ESCAPE_CODE(DCMSR_ANSI_ESCAPE_CODE_RESET)
stream << OFendl << OFendl;
/* patient related information */
if (!PatientName.isEmpty())
{
DCMSR_PRINT_HEADER_FIELD_START("Patient ", " : ")
stream << getPrintStringFromElement(PatientName, tmpString);
OFString patientStr;
if (!PatientSex.isEmpty())
patientStr += getPrintStringFromElement(PatientSex, tmpString);
if (!PatientBirthDate.isEmpty())
{
if (!patientStr.empty())
patientStr += ", ";
patientStr += dicomToReadableDate(getStringValueFromElement(PatientBirthDate, tmpString), string2);
}
if (!PatientID.isEmpty())
{
if (!patientStr.empty())
patientStr += ", ";
patientStr += '#';
patientStr += getPrintStringFromElement(PatientID, tmpString);
if (!IssuerOfPatientID.isEmpty())
{
patientStr += ":";
patientStr += getPrintStringFromElement(IssuerOfPatientID, tmpString);
}
}
if (!patientStr.empty())
stream << " (" << patientStr << ")";
DCMSR_PRINT_HEADER_FIELD_END
}
/* referring physician */
if (!ReferringPhysicianName.isEmpty())
{
DCMSR_PRINT_HEADER_FIELD_START("Referring Physician", " : ")
stream << getPrintStringFromElement(ReferringPhysicianName, tmpString);
DCMSR_PRINT_HEADER_FIELD_END
}
/* study-related information */
if (!StudyDescription.isEmpty())
{
DCMSR_PRINT_HEADER_FIELD_START("Study ", " : ")
stream << getPrintStringFromElement(StudyDescription, tmpString);
if (!StudyID.isEmpty())
stream << " (#" << getPrintStringFromElement(StudyID, tmpString) << ")";
DCMSR_PRINT_HEADER_FIELD_END
}
/* series-related information */
if (!SeriesDescription.isEmpty())
{
DCMSR_PRINT_HEADER_FIELD_START("Series ", " : ")
stream << getPrintStringFromElement(SeriesDescription, tmpString);
if (!SeriesNumber.isEmpty())
stream << " (#" << getPrintStringFromElement(SeriesNumber, tmpString) << ")";
DCMSR_PRINT_HEADER_FIELD_END
}
/* protocol name */
if (!ProtocolName.isEmpty())
{
DCMSR_PRINT_HEADER_FIELD_START("Protocol ", " : ")
stream << getPrintStringFromElement(ProtocolName, tmpString);
DCMSR_PRINT_HEADER_FIELD_END
}
/* manufacturer and device */
if (!Manufacturer.isEmpty())
{
DCMSR_PRINT_HEADER_FIELD_START("Manufacturer ", " : ")
stream << getPrintStringFromElement(Manufacturer, tmpString);
OFString deviceStr;
if (!ManufacturerModelName.isEmpty())
deviceStr += getPrintStringFromElement(ManufacturerModelName, tmpString);
if (!DeviceSerialNumber.isEmpty())
{
if (!deviceStr.empty())
deviceStr += ", ";
deviceStr += '#';
deviceStr += getPrintStringFromElement(DeviceSerialNumber, tmpString);
}
if (!deviceStr.empty())
stream << " (" << deviceStr << ")";
DCMSR_PRINT_HEADER_FIELD_END
}
/* not all SR IODs contain the SR Document General Module */
if (usesGeneralSRModules)
{
/* preliminary flag */
if (!PreliminaryFlag.isEmpty())
{
DCMSR_PRINT_HEADER_FIELD_START("Preliminary Flag ", " : ")
stream << getStringValueFromElement(PreliminaryFlag, tmpString);
DCMSR_PRINT_HEADER_FIELD_END
}
/* completion flag */
DCMSR_PRINT_HEADER_FIELD_START("Completion Flag ", " : ")
stream << getStringValueFromElement(CompletionFlag, tmpString);
DCMSR_PRINT_HEADER_FIELD_END
if (!CompletionFlagDescription.isEmpty())
{
DCMSR_PRINT_HEADER_FIELD_START(" ", " ")
stream << getPrintStringFromElement(CompletionFlagDescription, tmpString);
DCMSR_PRINT_HEADER_FIELD_END
}
/* predecessor documents */
if (!PredecessorDocuments.isEmpty())
{
DCMSR_PRINT_HEADER_FIELD_START("Predecessor Docs ", " : ")
stream << PredecessorDocuments.getNumberOfInstances();
DCMSR_PRINT_HEADER_FIELD_END
}
}
/* identical documents */
if (!IdenticalDocuments.isEmpty())
{
DCMSR_PRINT_HEADER_FIELD_START("Identical Docs ", " : ")
stream << IdenticalDocuments.getNumberOfInstances();
DCMSR_PRINT_HEADER_FIELD_END
}
/* referenced instances */
if (!ReferencedInstances.isEmpty())
{
DCMSR_PRINT_HEADER_FIELD_START("References Objects ", " : ")
stream << ReferencedInstances.getNumberOfItems();
DCMSR_PRINT_HEADER_FIELD_END
}
if (usesGeneralSRModules)
{
/* verification flag */
DCMSR_PRINT_HEADER_FIELD_START("Verification Flag ", " : ")
stream << getStringValueFromElement(VerificationFlag, tmpString);
DCMSR_PRINT_HEADER_FIELD_END
/* verifying observer */
const size_t obsCount = getNumberOfVerifyingObservers();
for (size_t i = 1; i <= obsCount; i++)
{
OFString dateTime, obsName, organization;
DSRCodedEntryValue obsCode;
if (getVerifyingObserver(i, dateTime, obsName, obsCode, organization).good())
{
if (i == 1)
{
DCMSR_PRINT_HEADER_FIELD_START("Verifying Observers", " : ")
} else {
DCMSR_PRINT_HEADER_FIELD_START(" ", " ")
}
stream << dicomToReadableDateTime(dateTime, tmpString) << ", " << obsName;
if (obsCode.isValid() || (flags & PF_printInvalidCodes))
{
stream << " ";
obsCode.print(stream, (flags & PF_printAllCodes) > 0 /*printCodeValue*/, flags);
}
stream << ", " << organization;
DCMSR_PRINT_HEADER_FIELD_END
}
}
}
/* content date and time */
if (!ContentDate.isEmpty() && !ContentTime.isEmpty())
{
DCMSR_PRINT_HEADER_FIELD_START("Content Date/Time ", " : ")
stream << dicomToReadableDate(getStringValueFromElement(ContentDate, tmpString), string2) << " ";
stream << dicomToReadableTime(getStringValueFromElement(ContentTime, tmpString), string2);
DCMSR_PRINT_HEADER_FIELD_END
}
stream << OFendl;
}
// --- dump document tree to stream ---
result = DocumentTree.print(stream, flags);
}
return result;
}
OFCondition DSRDocument::checkDatasetForReading(DcmItem &dataset,
E_DocumentType &documentType)
{
OFCondition result = EC_Normal;
OFString tmpString;
DcmUniqueIdentifier sopClassUID(DCM_SOPClassUID);
DcmCodeString modality(DCM_Modality);
/* check SOP class UID */
result = getAndCheckElementFromDataset(dataset, sopClassUID, "1", "1", "SOPCommonModule");
if (result.good())
{
documentType = sopClassUIDToDocumentType(getStringValueFromElement(sopClassUID, tmpString));
DCMSR_DEBUG("Value of SOP Class UID: " << tmpString);
if (documentType == DT_invalid)
{
DCMSR_ERROR("SOP Class UID does not match one of the known SR document classes");
result = SR_EC_UnknownDocumentType;
}
else if (!isDocumentTypeSupported(documentType))
{
DCMSR_ERROR("Unsupported SOP Class UID (not yet implemented)");
result = SR_EC_UnsupportedValue;
}
} else {
/* no SOP Class UID means no document type */
documentType = DT_invalid;
}
/* check modality */
if (result.good())
{
if (usesKeyObjectDocumentSeriesModule(documentType))
result = getAndCheckElementFromDataset(dataset, modality, "1", "1", "KeyObjectDocumentSeriesModule");
else
result = getAndCheckElementFromDataset(dataset, modality, "1", "1", "SRDocumentSeriesModule");
if (result.good())
{
if (getStringValueFromElement(modality, tmpString) != documentTypeToModality(documentType))
{
DCMSR_ERROR("Modality does not match '" << documentTypeToModality(documentType) << "' for "
<< documentTypeToReadableName(documentType));
}
}
}
return result;
}
OFCondition DSRDocument::read(DcmItem &dataset,
const size_t flags)
{
OFCondition result = EC_Normal;
E_DocumentType documentType = DT_invalid;
DCMSR_DEBUG("Reading SR document from DICOM dataset");
/* re-initialize SR document */
clear();
/* check SOP class UID and modality first */
result = checkDatasetForReading(dataset, documentType);
/* dataset is OK */
if (result.good())
{
OFString tmpString;
OFCondition searchCond = EC_Normal;
OFCondition obsSearchCond = EC_Normal;
/* type 3 element and attributes which have already been checked are not checked */
// --- SOP Common Module ---
getElementFromDataset(dataset, SOPClassUID); /* already checked */
getAndCheckElementFromDataset(dataset, SOPInstanceUID, "1", "1", "SOPCommonModule");
getAndCheckElementFromDataset(dataset, SpecificCharacterSet, "1-n", "1C", "SOPCommonModule");
getStringValueFromElement(SpecificCharacterSet, tmpString, -1 /* all components */);
/* currently, the VR checker in 'dcmdata' only supports ASCII and Latin-1 */
if (!tmpString.empty() && (tmpString != "ISO_IR 6") && (tmpString != "ISO_IR 100"))
DCMSR_WARN("The VR checker does not support this Specific Character Set: " << tmpString);
getAndCheckElementFromDataset(dataset, InstanceCreationDate, "1", "3", "SOPCommonModule");
getAndCheckElementFromDataset(dataset, InstanceCreationTime, "1", "3", "SOPCommonModule");
getAndCheckElementFromDataset(dataset, InstanceCreatorUID, "1", "3", "SOPCommonModule");
CodingSchemeIdentification.read(dataset, flags);
if (requiresTimezoneModule(documentType))
{
// --- Timezone Module ---
getAndCheckElementFromDataset(dataset, TimezoneOffsetFromUTC, "1", "1", "TimezoneModule");
} else {
// --- SOP Common Module ---
getAndCheckElementFromDataset(dataset, TimezoneOffsetFromUTC, "1", "3", "SOPCommonModule");
}
// --- General Study and Patient Module ---
readStudyData(dataset, flags);
if (requiresEnhancedEquipmentModule(documentType))
{
// --- Enhanced General Equipment Module ---
getAndCheckElementFromDataset(dataset, Manufacturer, "1", "1", "EnhancedGeneralEquipmentModule");
getAndCheckElementFromDataset(dataset, ManufacturerModelName, "1", "1", "EnhancedGeneralEquipmentModule");
getAndCheckElementFromDataset(dataset, DeviceSerialNumber, "1", "1", "EnhancedGeneralEquipmentModule");
getAndCheckElementFromDataset(dataset, SoftwareVersions, "1-n", "1", "EnhancedGeneralEquipmentModule");
} else {
// --- General Equipment Module ---
getAndCheckElementFromDataset(dataset, Manufacturer, "1", "2", "GeneralEquipmentModule");
getAndCheckElementFromDataset(dataset, ManufacturerModelName, "1", "3", "GeneralEquipmentModule");
getAndCheckElementFromDataset(dataset, DeviceSerialNumber, "1", "3", "GeneralEquipmentModule");
getAndCheckElementFromDataset(dataset, SoftwareVersions, "1-n", "3", "GeneralEquipmentModule");
}
// --- Synchronization Module ---
if (requiresSynchronizationModule(documentType) /* either the IOD requires this module */ ||
dataset.tagExistsWithValue(DCM_SynchronizationFrameOfReferenceUID) || dataset.tagExistsWithValue(DCM_SynchronizationTrigger) ||
dataset.tagExistsWithValue(DCM_AcquisitionTimeSynchronized) /* or all attributes should be absent */ )
{
getAndCheckElementFromDataset(dataset, SynchronizationFrameOfReferenceUID, "1", "1", "SynchronizationModule");
getAndCheckElementFromDataset(dataset, SynchronizationTrigger, "1", "1", "SynchronizationModule");
getAndCheckElementFromDataset(dataset, AcquisitionTimeSynchronized, "1", "1", "SynchronizationModule");
}
// --- SR Document Series Module / Key Object Document Series Module ---
getElementFromDataset(dataset, Modality); /* already checked */
if (usesKeyObjectDocumentSeriesModule(documentType))
{
getAndCheckElementFromDataset(dataset, SeriesInstanceUID, "1", "1", "KeyObjectDocumentSeriesModule");
getAndCheckElementFromDataset(dataset, SeriesNumber, "1", "1", "KeyObjectDocumentSeriesModule");
getAndCheckElementFromDataset(dataset, SeriesDate, "1", "3", "KeyObjectDocumentSeriesModule");
getAndCheckElementFromDataset(dataset, SeriesTime, "1", "3", "KeyObjectDocumentSeriesModule");
getAndCheckElementFromDataset(dataset, ProtocolName, "1", "3", "KeyObjectDocumentSeriesModule");
getAndCheckElementFromDataset(dataset, SeriesDescription, "1", "3", "KeyObjectDocumentSeriesModule");
/* need to check sequence in two steps (avoids additional getAndCheck... method) */
searchCond = getElementFromDataset(dataset, ReferencedPerformedProcedureStep);
checkElementValue(ReferencedPerformedProcedureStep, "1", "2", searchCond, "KeyObjectDocumentSeriesModule");
} else {
getAndCheckElementFromDataset(dataset, SeriesInstanceUID, "1", "1", "SRDocumentSeriesModule");
getAndCheckElementFromDataset(dataset, SeriesNumber, "1", "1", "SRDocumentSeriesModule");
getAndCheckElementFromDataset(dataset, SeriesDate, "1", "3", "SRDocumentSeriesModule");
getAndCheckElementFromDataset(dataset, SeriesTime, "1", "3", "SRDocumentSeriesModule");
getAndCheckElementFromDataset(dataset, ProtocolName, "1", "3", "SRDocumentSeriesModule");
getAndCheckElementFromDataset(dataset, SeriesDescription, "1", "3", "SRDocumentSeriesModule");
/* need to check sequence in two steps (avoids additional getAndCheck... method) */
searchCond = getElementFromDataset(dataset, ReferencedPerformedProcedureStep);
checkElementValue(ReferencedPerformedProcedureStep, "1", "2", searchCond, "SRDocumentSeriesModule");
}
/* remove possible signature sequences */
removeAttributeFromSequence(ReferencedPerformedProcedureStep, DCM_MACParametersSequence);
removeAttributeFromSequence(ReferencedPerformedProcedureStep, DCM_DigitalSignaturesSequence);
// --- SR Document General Module / Key Object Document Module ---
if (usesKeyObjectDocumentModule(documentType))
{
getAndCheckElementFromDataset(dataset, InstanceNumber, "1", "1", "KeyObjectDocumentModule");
getAndCheckElementFromDataset(dataset, ContentDate, "1", "1", "KeyObjectDocumentModule");
getAndCheckElementFromDataset(dataset, ContentTime, "1", "1", "KeyObjectDocumentModule");
} else {
getAndCheckElementFromDataset(dataset, InstanceNumber, "1", "1", "SRDocumentGeneralModule");
getAndCheckElementFromDataset(dataset, ContentDate, "1", "1", "SRDocumentGeneralModule");
getAndCheckElementFromDataset(dataset, ContentTime, "1", "1", "SRDocumentGeneralModule");
getAndCheckElementFromDataset(dataset, PreliminaryFlag, "1", "3", "SRDocumentGeneralModule");
getAndCheckElementFromDataset(dataset, CompletionFlag, "1", "1", "SRDocumentGeneralModule");
getAndCheckElementFromDataset(dataset, CompletionFlagDescription, "1", "3", "SRDocumentGeneralModule");
getAndCheckElementFromDataset(dataset, VerificationFlag, "1", "1", "SRDocumentGeneralModule");
obsSearchCond = getElementFromDataset(dataset, VerifyingObserver);
PredecessorDocuments.read(dataset, flags);
/* need to check sequence in two steps (avoids additional getAndCheck... method) */
searchCond = getElementFromDataset(dataset, PerformedProcedureCode);
checkElementValue(PerformedProcedureCode, "1-n", "2", searchCond, "SRDocumentGeneralModule");
PertinentOtherEvidence.read(dataset, flags);
ReferencedInstances.read(dataset, flags);
}
IdenticalDocuments.read(dataset, flags);
CurrentRequestedProcedureEvidence.read(dataset, flags);
/* remove possible signature sequences */
removeAttributeFromSequence(VerifyingObserver, DCM_MACParametersSequence);
removeAttributeFromSequence(VerifyingObserver, DCM_DigitalSignaturesSequence);
removeAttributeFromSequence(PerformedProcedureCode, DCM_MACParametersSequence);
removeAttributeFromSequence(PerformedProcedureCode, DCM_DigitalSignaturesSequence);
/* update internal enumerated values and perform additional checks */
/* not all SR IODs contain the SR Document General Module */
if (usesSRDocumentGeneralModule(documentType))
{
/* get and check PreliminaryFlag (if present) */
if (!PreliminaryFlag.isEmpty())
{
PreliminaryFlagEnum = enumeratedValueToPreliminaryFlag(getStringValueFromElement(PreliminaryFlag, tmpString));
if (PreliminaryFlagEnum == PF_invalid)
printUnknownValueWarningMessage("PreliminaryFlag", tmpString.c_str());
}
/* get and check CompletionFlag */
CompletionFlagEnum = enumeratedValueToCompletionFlag(getStringValueFromElement(CompletionFlag, tmpString));
if (CompletionFlagEnum == CF_invalid)
printUnknownValueWarningMessage("CompletionFlag", tmpString.c_str());
else if ((documentType == DT_XRayRadiationDoseSR) && (CompletionFlagEnum != CF_Complete))
DCMSR_WARN("Invalid value for Completion Flag, should be 'COMPLETE' for X-Ray Radiation Dose SR");
/* get and check VerificationFlag / VerifyingObserverSequence */
VerificationFlagEnum = enumeratedValueToVerificationFlag(getStringValueFromElement(VerificationFlag, tmpString));
if (VerificationFlagEnum == VF_invalid)
printUnknownValueWarningMessage("VerificationFlag", tmpString.c_str());
else if (VerificationFlagEnum == VF_Verified)
checkElementValue(VerifyingObserver, "1-n", "1", obsSearchCond, "SRDocumentGeneralModule");
}
getStringValueFromElement(SpecificCharacterSet, tmpString, -1 /* all components */);
SpecificCharacterSetEnum = definedTermToCharacterSet(tmpString);
/* check SpecificCharacterSet */
if ((SpecificCharacterSetEnum == CS_invalid) && !tmpString.empty())
printUnknownValueWarningMessage("SpecificCharacterSet", tmpString.c_str());
/* read SR document tree */
if (result.good())
result = DocumentTree.read(dataset, documentType, flags);
}
return result;
}
OFCondition DSRDocument::readPatientData(DcmItem &dataset,
const size_t /*flags*/)
{
// --- Patient Module ---
getAndCheckElementFromDataset(dataset, PatientName, "1", "2", "PatientModule");
getAndCheckElementFromDataset(dataset, PatientID, "1", "2", "PatientModule");
getAndCheckElementFromDataset(dataset, IssuerOfPatientID, "1", "3", "PatientModule");
getAndCheckElementFromDataset(dataset, PatientBirthDate, "1", "2", "PatientModule");
getAndCheckElementFromDataset(dataset, PatientSex, "1", "2", "PatientModule");
/* always return success */
return EC_Normal;
}
OFCondition DSRDocument::readStudyData(DcmItem &dataset,
const size_t flags)
{
// --- General Study Module ---
getAndCheckElementFromDataset(dataset, StudyInstanceUID, "1", "1", "GeneralStudyModule");
getAndCheckElementFromDataset(dataset, StudyDate, "1", "2", "GeneralStudyModule");
getAndCheckElementFromDataset(dataset, StudyTime, "1", "2", "GeneralStudyModule");
getAndCheckElementFromDataset(dataset, ReferringPhysicianName, "1", "2", "GeneralStudyModule");
getAndCheckElementFromDataset(dataset, StudyID, "1", "2", "GeneralStudyModule");
getAndCheckElementFromDataset(dataset, AccessionNumber, "1", "2", "GeneralStudyModule");
getAndCheckElementFromDataset(dataset, StudyDescription, "1", "3", "GeneralStudyModule");
// --- Patient Study Module ---
getAndCheckElementFromDataset(dataset, PatientSize, "1", "3", "PatientStudyModule");
getAndCheckElementFromDataset(dataset, PatientWeight, "1", "3", "PatientStudyModule");
/* also read data from Patient Module */
return readPatientData(dataset, flags);
}
OFCondition DSRDocument::write(DcmItem &dataset,
DcmStack *markedItems)
{
OFCondition result = EC_Normal;
/* only write valid documents */
if (isValid())
{
DCMSR_DEBUG("Writing SR document to DICOM dataset");
/* update all DICOM attributes */
updateAttributes();
/* checking particular values */
if ((getDocumentType() == DT_XRayRadiationDoseSR) && (CompletionFlagEnum != CF_Complete))
DCMSR_WARN("Invalid value for Completion Flag, should be 'COMPLETE' for X-Ray Radiation Dose SR");
/* write general document attributes */
// --- SOP Common Module ---
addElementToDataset(result, dataset, new DcmUniqueIdentifier(SOPClassUID), "1", "1", "SOPCommonModule");
addElementToDataset(result, dataset, new DcmUniqueIdentifier(SOPInstanceUID), "1", "1", "SOPCommonModule");
/* never write specific character set for ASCII (default character repertoire) */
if (SpecificCharacterSetEnum != CS_ASCII)
addElementToDataset(result, dataset, new DcmCodeString(SpecificCharacterSet), "1-n", "1C", "SOPCommonModule");
addElementToDataset(result, dataset, new DcmDate(InstanceCreationDate), "1", "3", "SOPCommonModule");
addElementToDataset(result, dataset, new DcmTime(InstanceCreationTime), "1", "3", "SOPCommonModule");
addElementToDataset(result, dataset, new DcmUniqueIdentifier(InstanceCreatorUID), "1", "3", "SOPCommonModule");
CodingSchemeIdentification.write(dataset);
if (requiresTimezoneModule(getDocumentType()))
{
// --- Timezone Module ---
addElementToDataset(result, dataset, new DcmShortString(TimezoneOffsetFromUTC), "1", "1", "TimezoneModule");
} else {
// --- SOP Common Module ---
addElementToDataset(result, dataset, new DcmShortString(TimezoneOffsetFromUTC), "1", "3", "SOPCommonModule");
}
// --- General Study Module ---
addElementToDataset(result, dataset, new DcmUniqueIdentifier(StudyInstanceUID), "1", "1", "GeneralStudyModule");
addElementToDataset(result, dataset, new DcmDate(StudyDate), "1", "2", "GeneralStudyModule");
addElementToDataset(result, dataset, new DcmTime(StudyTime), "1", "2", "GeneralStudyModule");
addElementToDataset(result, dataset, new DcmPersonName(ReferringPhysicianName), "1", "2", "GeneralStudyModule");
addElementToDataset(result, dataset, new DcmShortString(StudyID), "1", "2", "GeneralStudyModule");
addElementToDataset(result, dataset, new DcmShortString(AccessionNumber), "1", "2", "GeneralStudyModule");
addElementToDataset(result, dataset, new DcmLongString(StudyDescription), "1", "3", "GeneralStudyModule");
// --- Patient Module ---
addElementToDataset(result, dataset, new DcmPersonName(PatientName), "1", "2", "PatientModule");
addElementToDataset(result, dataset, new DcmLongString(PatientID), "1", "2", "PatientModule");
addElementToDataset(result, dataset, new DcmLongString(IssuerOfPatientID), "1", "3", "PatientModule");
addElementToDataset(result, dataset, new DcmDate(PatientBirthDate), "1", "2", "PatientModule");
addElementToDataset(result, dataset, new DcmCodeString(PatientSex), "1", "2", "PatientModule");
// --- Patient Study Module ---
addElementToDataset(result, dataset, new DcmDecimalString(PatientSize), "1", "3", "PatientStudyModule");
addElementToDataset(result, dataset, new DcmDecimalString(PatientWeight), "1", "3", "PatientStudyModule");
if (requiresEnhancedEquipmentModule(getDocumentType()))
{
// --- Enhanced General Equipment Module ---
addElementToDataset(result, dataset, new DcmLongString(Manufacturer), "1", "1", "EnhancedGeneralEquipmentModule");
addElementToDataset(result, dataset, new DcmLongString(ManufacturerModelName), "1", "1", "EnhancedGeneralEquipmentModule");
addElementToDataset(result, dataset, new DcmLongString(DeviceSerialNumber), "1", "1", "EnhancedGeneralEquipmentModule");
addElementToDataset(result, dataset, new DcmLongString(SoftwareVersions), "1-n", "1", "EnhancedGeneralEquipmentModule");
} else {
// --- General Equipment Module ---
addElementToDataset(result, dataset, new DcmLongString(Manufacturer), "1", "2", "GeneralEquipmentModule");
addElementToDataset(result, dataset, new DcmLongString(ManufacturerModelName), "1", "3", "GeneralEquipmentModule");
addElementToDataset(result, dataset, new DcmLongString(DeviceSerialNumber), "1", "3", "GeneralEquipmentModule");
addElementToDataset(result, dataset, new DcmLongString(SoftwareVersions), "1-n", "3", "GeneralEquipmentModule");
}
// --- Synchronization Module ---
if (requiresSynchronizationModule(getDocumentType()) /* module required for some IODs */ ||
!SynchronizationFrameOfReferenceUID.isEmpty() || !SynchronizationTrigger.isEmpty() || !AcquisitionTimeSynchronized.isEmpty())
{
addElementToDataset(result, dataset, new DcmUniqueIdentifier(SynchronizationFrameOfReferenceUID), "1", "1", "SynchronizationModule");
addElementToDataset(result, dataset, new DcmCodeString(SynchronizationTrigger), "1", "1", "SynchronizationModule");
addElementToDataset(result, dataset, new DcmCodeString(AcquisitionTimeSynchronized), "1", "1", "SynchronizationModule");
}
// --- SR Document Series Module / Key Object Document Series Module ---
if (usesKeyObjectDocumentSeriesModule(getDocumentType()))
{
addElementToDataset(result, dataset, new DcmCodeString(Modality), "1", "1", "KeyObjectDocumentSeriesModule");
addElementToDataset(result, dataset, new DcmUniqueIdentifier(SeriesInstanceUID), "1", "1", "KeyObjectDocumentSeriesModule");
addElementToDataset(result, dataset, new DcmIntegerString(SeriesNumber), "1", "1", "KeyObjectDocumentSeriesModule");
addElementToDataset(result, dataset, new DcmDate(SeriesDate), "1", "3", "KeyObjectDocumentSeriesModule");
addElementToDataset(result, dataset, new DcmTime(SeriesTime), "1", "3", "KeyObjectDocumentSeriesModule");
addElementToDataset(result, dataset, new DcmLongString(ProtocolName), "1", "3", "KeyObjectDocumentSeriesModule");
addElementToDataset(result, dataset, new DcmLongString(SeriesDescription), "1", "3", "KeyObjectDocumentSeriesModule");
/* always write empty sequence since not yet fully supported */
ReferencedPerformedProcedureStep.clear();
addElementToDataset(result, dataset, new DcmSequenceOfItems(ReferencedPerformedProcedureStep), "1", "2", "KeyObjectDocumentSeriesModule");
} else {
addElementToDataset(result, dataset, new DcmCodeString(Modality), "1", "1", "SRDocumentSeriesModule");
addElementToDataset(result, dataset, new DcmUniqueIdentifier(SeriesInstanceUID), "1", "1", "SRDocumentSeriesModule");
addElementToDataset(result, dataset, new DcmIntegerString(SeriesNumber), "1", "1", "SRDocumentSeriesModule");
addElementToDataset(result, dataset, new DcmDate(SeriesDate), "1", "3", "SRDocumentSeriesModule");
addElementToDataset(result, dataset, new DcmTime(SeriesTime), "1", "3", "SRDocumentSeriesModule");
addElementToDataset(result, dataset, new DcmLongString(ProtocolName), "1", "3", "SRDocumentSeriesModule");
addElementToDataset(result, dataset, new DcmLongString(SeriesDescription), "1", "3", "SRDocumentSeriesModule");
/* always write empty sequence since not yet fully supported */
ReferencedPerformedProcedureStep.clear();
addElementToDataset(result, dataset, new DcmSequenceOfItems(ReferencedPerformedProcedureStep), "1", "2", "SRDocumentSeriesModule");
}
// --- SR Document General Module / Key Object Document Module ---
if (usesKeyObjectDocumentModule(getDocumentType()))
{
addElementToDataset(result, dataset, new DcmIntegerString(InstanceNumber), "1", "1", "KeyObjectDocumentModule");
addElementToDataset(result, dataset, new DcmDate(ContentDate), "1", "1", "KeyObjectDocumentModule");
addElementToDataset(result, dataset, new DcmTime(ContentTime), "1", "1", "KeyObjectDocumentModule");
} else {
addElementToDataset(result, dataset, new DcmIntegerString(InstanceNumber), "1", "1", "SRDocumentGeneralModule");
addElementToDataset(result, dataset, new DcmDate(ContentDate), "1", "1", "SRDocumentGeneralModule");
addElementToDataset(result, dataset, new DcmTime(ContentTime), "1", "1", "SRDocumentGeneralModule");
addElementToDataset(result, dataset, new DcmCodeString(PreliminaryFlag), "1", "3", "SRDocumentGeneralModule");
addElementToDataset(result, dataset, new DcmCodeString(CompletionFlag), "1", "1", "SRDocumentGeneralModule");
addElementToDataset(result, dataset, new DcmLongString(CompletionFlagDescription), "1", "3", "SRDocumentGeneralModule");
addElementToDataset(result, dataset, new DcmCodeString(VerificationFlag), "1", "1", "SRDocumentGeneralModule");
if (VerificationFlagEnum == VF_Verified)
addElementToDataset(result, dataset, new DcmSequenceOfItems(VerifyingObserver), "1-n", "1", "SRDocumentGeneralModule");
if (result.good())
PredecessorDocuments.write(dataset); /* optional */
/* always write empty sequence since not yet fully supported */
PerformedProcedureCode.clear();
addElementToDataset(result, dataset, new DcmSequenceOfItems(PerformedProcedureCode), "1-n", "2", "SRDocumentGeneralModule");
if (result.good())
result = PertinentOtherEvidence.write(dataset);
if (result.good())
result = ReferencedInstances.write(dataset);
}
if (result.good())
IdenticalDocuments.write(dataset); /* optional */
if (result.good())
result = CurrentRequestedProcedureEvidence.write(dataset);
/* write SR document tree */
if (result.good())
result = DocumentTree.write(dataset, markedItems);
} else
result = SR_EC_InvalidDocument;
return result;
}
OFCondition DSRDocument::readXML(const OFString &filename,
const size_t flags)
{
DSRXMLDocument doc;
DCMSR_DEBUG("Reading SR document from XML format");
/* read, parse and validate XML document */
OFCondition result = doc.read(filename, flags);
if (result.good())
{
/* re-initialize SR document */
clear();
/* start with document root node */
DSRXMLCursor cursor(doc.getRootNode());
/* check whether we really parse a "report" document */
result = doc.checkNode(cursor, "report");
if (result.good())
{
/* goto sub-element "sopclass" (first child node!) */
result = doc.checkNode(cursor.gotoChild(), "sopclass");
if (result.good())
{
/* determine document type (SOP class) */
result = doc.getElementFromAttribute(cursor, SOPClassUID, "uid");
if (result.good())
{
OFString sopClassUID;
getSOPClassUID(sopClassUID);
/* change document type (also checks for support) */
result = changeDocumentType(sopClassUIDToDocumentType(sopClassUID));
if (result.good())
{
/* proceed with document header */
result = readXMLDocumentHeader(doc, cursor.gotoNext(), flags);
} else
DCMSR_ERROR("Unknown/Unsupported SOP Class UID");
}
}
}
}
return result;
}
OFCondition DSRDocument::readXMLDocumentHeader(DSRXMLDocument &doc,
DSRXMLCursor cursor,
const size_t flags)
{
OFCondition result = SR_EC_InvalidDocument;
if (doc.valid() && cursor.valid())
{
result = EC_Normal;
/* iterate over all nodes */
while (cursor.valid() && result.good())
{
/* check for known element tags */
if (doc.matchNode(cursor, "charset"))
{
/* use "charset" to decode special characters (has to be at the beginning) */
if (!doc.encodingHandlerValid())
{
OFString tmpString;
/* check for known character set */
setSpecificCharacterSet(doc.getStringFromNodeContent(cursor, tmpString));
if (tmpString.empty())
DCMSR_WARN("Empty value for 'charset' ... ignoring");
else {
const char *encString = characterSetToXMLName(SpecificCharacterSetEnum);
if ((strcmp(encString, "?") == 0) || doc.setEncodingHandler(encString).bad())
DCMSR_WARN("Character set '" << tmpString << "' not supported");
}
} else {
/* only one "charset" node allowed */
doc.printUnexpectedNodeWarning(cursor);
}
}
else if (doc.matchNode(cursor, "timezone"))
{
doc.getElementFromNodeContent(cursor, TimezoneOffsetFromUTC, NULL, OFTrue /*encoding*/);
}
else if (doc.matchNode(cursor, "modality"))
{
OFString tmpString;
/* compare the XML node content */
if (doc.getStringFromNodeContent(cursor, tmpString) != documentTypeToModality(getDocumentType()))
DCMSR_WARN("Invalid value for 'modality' ... ignoring");
}
else if (doc.matchNode(cursor, "device"))
{
doc.getElementFromNodeContent(doc.getNamedChildNode(cursor, "manufacturer", OFFalse /*required*/), Manufacturer, NULL, OFTrue /*encoding*/);
doc.getElementFromNodeContent(doc.getNamedChildNode(cursor, "model"), ManufacturerModelName, NULL, OFTrue /*encoding*/);
doc.getElementFromNodeContent(doc.getNamedChildNode(cursor, "serial", OFFalse /*required*/), DeviceSerialNumber, NULL, OFTrue /*encoding*/);
doc.getElementFromNodeContent(doc.getNamedChildNode(cursor, "version", OFFalse /*required*/), SoftwareVersions, NULL, OFTrue /*encoding*/);
}
else if (doc.matchNode(cursor, "manufacturer"))
doc.getElementFromNodeContent(cursor, Manufacturer, "manufacturer", OFTrue /*encoding*/);
else if (doc.matchNode(cursor, "synchronization"))
{
doc.getElementFromAttribute(cursor, SynchronizationFrameOfReferenceUID, "uid");
doc.getElementFromNodeContent(doc.getNamedChildNode(cursor, "trigger"), SynchronizationTrigger);
doc.getElementFromNodeContent(doc.getNamedChildNode(cursor, "acquisitiontime"), AcquisitionTimeSynchronized);
}
else if (doc.matchNode(cursor, "referringphysician"))
{
/* goto sub-element "name" */
const DSRXMLCursor childNode = doc.getNamedChildNode(cursor, "name");
if (childNode.valid())
{
/* Referring Physician's Name */
OFString tmpString;
DSRPNameTreeNode::getValueFromXMLNodeContent(doc, childNode.getChild(), tmpString);
ReferringPhysicianName.putOFStringArray(tmpString);
}
}
else if (doc.matchNode(cursor, "patient"))
result = readXMLPatientData(doc, cursor.getChild(), flags);
else if (doc.matchNode(cursor, "study"))
result = readXMLStudyData(doc, cursor, flags);
else if (doc.matchNode(cursor, "series"))
result = readXMLSeriesData(doc, cursor, flags);
else if (doc.matchNode(cursor, "instance"))
result = readXMLInstanceData(doc, cursor, flags);
else if (doc.matchNode(cursor, "coding"))
{
const DSRXMLCursor childNode = cursor.getChild();
if (childNode.valid())
result = CodingSchemeIdentification.readXML(doc, childNode, flags);
}
else if (doc.matchNode(cursor, "evidence"))
{
OFString typeString;
/* check "type" attribute for corresponding sequence */
if (doc.getStringFromAttribute(cursor, typeString, "type") == "Current Requested Procedure")
result = CurrentRequestedProcedureEvidence.readXML(doc, cursor.getChild(), flags);
else if (typeString == "Pertinent Other")
{
if (usesSRDocumentGeneralModule(getDocumentType()))
result = PertinentOtherEvidence.readXML(doc, cursor.getChild(), flags);
else
doc.printUnexpectedNodeWarning(cursor);
} else // none of the standard defined evidence types
printUnknownValueWarningMessage("Evidence type", typeString.c_str());
}
else if (doc.matchNode(cursor, "reference"))
{
const DSRXMLCursor childNode = cursor.getChild();
if (childNode.valid())
result = ReferencedInstances.readXML(doc, childNode, flags);
}
else if (doc.matchNode(cursor, "document"))
result = readXMLDocumentData(doc, cursor.getChild(), flags);
else
doc.printUnexpectedNodeWarning(cursor);
/* print node error message (if any) */
doc.printGeneralNodeError(cursor, result);
/* proceed with next node */
cursor.gotoNext();
}
}
return result;
}
OFCondition DSRDocument::readXMLPatientData(const DSRXMLDocument &doc,
DSRXMLCursor cursor,
const size_t /*flags*/)
{
OFCondition result = SR_EC_InvalidDocument;
if (cursor.valid())
{
OFString tmpString;
result = EC_Normal;
/* iterate over all nodes */
while (cursor.valid())
{
/* check for known element tags (all type 2) */
if (doc.matchNode(cursor, "name"))
{
/* Patient's Name */
DSRPNameTreeNode::getValueFromXMLNodeContent(doc, cursor.getChild(), tmpString);
PatientName.putOFStringArray(tmpString);
}
else if (doc.matchNode(cursor, "birthday"))
{
/* Patient's Birth Date */
DSRDateTreeNode::getValueFromXMLNodeContent(doc, doc.getNamedChildNode(cursor, "date"), tmpString);
PatientBirthDate.putOFStringArray(tmpString);
}
else if (doc.getElementFromNodeContent(cursor, PatientID, "id").bad() &&
doc.getElementFromNodeContent(cursor, IssuerOfPatientID, "issuer").bad() &&
doc.getElementFromNodeContent(cursor, PatientSex, "sex").bad() &&
/* strictly speaking, Patient's Size and Weight belong to the Study IE */
doc.getElementFromNodeContent(cursor, PatientSize, "size").bad() &&
doc.getElementFromNodeContent(cursor, PatientWeight, "weight").bad())
{
doc.printUnexpectedNodeWarning(cursor);
}
/* proceed with next node */
cursor.gotoNext();
}
}
return result;
}
OFCondition DSRDocument::readXMLStudyData(const DSRXMLDocument &doc,
DSRXMLCursor cursor,
const size_t flags)
{
OFCondition result = SR_EC_InvalidDocument;
if (cursor.valid())
{
OFString tmpString;
/* get Study Instance UID from XML attribute */
if (flags & XF_acceptEmptyStudySeriesInstanceUID)
{
if (doc.getElementFromAttribute(cursor, StudyInstanceUID, "uid", OFFalse /*encoding*/, OFFalse /*required*/).bad())
doc.printMissingAttributeWarning(cursor, "uid");
result = EC_Normal;
} else
result = doc.getElementFromAttribute(cursor, StudyInstanceUID, "uid");
/* goto first sub-element */
cursor.gotoChild();
/* iterate over all nodes */
while (cursor.valid())
{
/* check for known element tags */
if (doc.matchNode(cursor, "accession"))
{
/* goto sub-element "number" */
doc.getElementFromNodeContent(doc.getNamedChildNode(cursor, "number"), AccessionNumber);
}
else if (doc.matchNode(cursor, "date"))
{
DSRDateTreeNode::getValueFromXMLNodeContent(doc, cursor, tmpString);
StudyDate.putOFStringArray(tmpString);
}
else if (doc.matchNode(cursor, "time"))
{
DSRTimeTreeNode::getValueFromXMLNodeContent(doc, cursor, tmpString);
StudyTime.putOFStringArray(tmpString);
}
else if (doc.getElementFromNodeContent(cursor, StudyID, "id").bad() &&
doc.getElementFromNodeContent(cursor, StudyDescription, "description", OFTrue /*encoding*/).bad())
{
doc.printUnexpectedNodeWarning(cursor);
}
/* proceed with next node */
cursor.gotoNext();
}
/* check required element values */
checkElementValue(StudyInstanceUID, "1", "1");
}
return result;
}
OFCondition DSRDocument::readXMLSeriesData(const DSRXMLDocument &doc,
DSRXMLCursor cursor,
const size_t flags)
{
OFCondition result = SR_EC_InvalidDocument;
if (cursor.valid())
{
OFString tmpString;
/* get Series Instance UID from XML attribute */
if (flags & XF_acceptEmptyStudySeriesInstanceUID)
{
if (doc.getElementFromAttribute(cursor, SeriesInstanceUID, "uid", OFFalse /*encoding*/, OFFalse /*required*/).bad())
doc.printMissingAttributeWarning(cursor, "uid");
result = EC_Normal;
} else
result = doc.getElementFromAttribute(cursor, SeriesInstanceUID, "uid");
/* goto first sub-element */
cursor.gotoChild();
/* iterate over all nodes */
while (cursor.valid())
{
/* check for known element tags */
if (doc.matchNode(cursor, "date"))
{
DSRDateTreeNode::getValueFromXMLNodeContent(doc, cursor, tmpString);
SeriesDate.putOFStringArray(tmpString);
}
else if (doc.matchNode(cursor, "time"))
{
DSRTimeTreeNode::getValueFromXMLNodeContent(doc, cursor, tmpString);
SeriesTime.putOFStringArray(tmpString);
}
else if (doc.getElementFromNodeContent(cursor, SeriesNumber, "number").bad() &&
doc.getElementFromNodeContent(cursor, ProtocolName, "protocol", OFTrue /*encoding*/).bad() &&
doc.getElementFromNodeContent(cursor, SeriesDescription, "description", OFTrue /*encoding*/).bad())
{
doc.printUnexpectedNodeWarning(cursor);
}
/* proceed with next node */
cursor.gotoNext();
}
/* check required element values */
checkElementValue(SeriesInstanceUID, "1", "1");
checkElementValue(SeriesNumber, "1", "1");
}
return result;
}
OFCondition DSRDocument::readXMLInstanceData(const DSRXMLDocument &doc,
DSRXMLCursor cursor,
const size_t flags)
{
OFCondition result = SR_EC_InvalidDocument;
if (cursor.valid())
{
OFString tmpString;
/* get SOP Instance UID from XML attribute */
if (flags & XF_acceptEmptyStudySeriesInstanceUID)
{
if (doc.getElementFromAttribute(cursor, SOPInstanceUID, "uid", OFFalse /*encoding*/, OFFalse /*required*/).bad())
doc.printMissingAttributeWarning(cursor, "uid");
result = EC_Normal;
} else
result = doc.getElementFromAttribute(cursor, SOPInstanceUID, "uid");
/* goto first sub-element */
cursor.gotoChild();
/* iterate over all nodes */
while (cursor.valid())
{
/* check for known element tags */
if (doc.matchNode(cursor, "creation"))
{
/* Instance Creator UID */
doc.getElementFromAttribute(cursor, InstanceCreatorUID, "uid", OFFalse /*encoding*/, OFFalse /*required*/);
/* Instance Creation Date */
DSRDateTreeNode::getValueFromXMLNodeContent(doc, doc.getNamedChildNode(cursor, "date", OFFalse /*required*/), tmpString);
InstanceCreationDate.putOFStringArray(tmpString);
/* Instance Creation Time */
DSRTimeTreeNode::getValueFromXMLNodeContent(doc, doc.getNamedChildNode(cursor, "time", OFFalse /*required*/), tmpString);
InstanceCreationTime.putOFStringArray(tmpString);
}
else if (doc.getElementFromNodeContent(cursor, InstanceNumber, "number").bad())
doc.printUnexpectedNodeWarning(cursor);
/* proceed with next node */
cursor.gotoNext();
}
/* check required element values */
checkElementValue(SOPInstanceUID, "1", "1");
}
return result;
}
OFCondition DSRDocument::readXMLDocumentData(const DSRXMLDocument &doc,
DSRXMLCursor cursor,
const size_t flags)
{
OFCondition result = SR_EC_InvalidDocument;
if (cursor.valid())
{
OFString tmpString;
/* check whether general SR modules are used */
const OFBool usesGeneralSRModules = usesSRDocumentGeneralModule(getDocumentType());
result = EC_Normal;
/* iterate over all nodes */
while (cursor.valid() && result.good())
{
/* check for known element tags
(not all SR IODs contain the SR Document General Module) */
if (usesGeneralSRModules && doc.matchNode(cursor, "preliminary"))
{
/* Preliminary Flag */
PreliminaryFlagEnum = enumeratedValueToPreliminaryFlag(doc.getStringFromAttribute(cursor, tmpString, "flag"));
if (PreliminaryFlagEnum == PF_invalid)
printUnknownValueWarningMessage("PreliminaryFlag", tmpString.c_str());
}
else if (usesGeneralSRModules && doc.matchNode(cursor, "completion"))
{
/* Completion Flag */
CompletionFlagEnum = enumeratedValueToCompletionFlag(doc.getStringFromAttribute(cursor, tmpString, "flag"));
if (CompletionFlagEnum != CF_invalid)
{
/* Completion Flag Description (optional) */
const DSRXMLCursor childCursor = doc.getNamedChildNode(cursor, "description", OFFalse /*required*/);
if (childCursor.valid())
doc.getElementFromNodeContent(childCursor, CompletionFlagDescription, NULL /*name*/, OFTrue /*encoding*/);
} else
printUnknownValueWarningMessage("CompletionFlag", tmpString.c_str());
}
else if (usesGeneralSRModules && doc.matchNode(cursor, "verification"))
{
/* Verification Flag */
VerificationFlagEnum = enumeratedValueToVerificationFlag(doc.getStringFromAttribute(cursor, tmpString, "flag"));
if (VerificationFlagEnum != VF_invalid)
{
/* Verifying Observers (required if VERIFIED) */
result = readXMLVerifyingObserverData(doc, cursor.getChild(), flags);
/* allow absence in case of UNVERIFIED */
if (VerificationFlagEnum == VF_Unverified)
result = EC_Normal;
} else
printUnknownValueWarningMessage("VerificationFlag", tmpString.c_str());
}
else if (usesGeneralSRModules && doc.matchNode(cursor, "predecessor"))
{
/* Predecessor Documents Sequence (optional) */
result = PredecessorDocuments.readXML(doc, cursor.getChild(), flags);
}
else if (doc.matchNode(cursor, "identical"))
{
/* Identical Documents Sequence (optional) */
result = IdenticalDocuments.readXML(doc, cursor.getChild(), flags);
}
else if (doc.matchNode(cursor, "content"))
{
/* Content Date */
DSRDateTreeNode::getValueFromXMLNodeContent(doc, doc.getNamedChildNode(cursor, "date"), tmpString);
ContentDate.putOFStringArray(tmpString);
/* Content Time */
DSRTimeTreeNode::getValueFromXMLNodeContent(doc, doc.getNamedChildNode(cursor, "time"), tmpString);
ContentTime.putOFStringArray(tmpString);
/* proceed with document tree */
result = DocumentTree.readXML(doc, cursor.getChild(), flags);
} else
doc.printUnexpectedNodeWarning(cursor);
/* print node error message (if any) */
doc.printGeneralNodeError(cursor, result);
/* proceed with next node */
cursor.gotoNext();
}
}
return result;
}
OFCondition DSRDocument::readXMLVerifyingObserverData(const DSRXMLDocument &doc,
DSRXMLCursor cursor,
const size_t flags)
{
OFCondition result = SR_EC_InvalidDocument;
if (cursor.valid())
{
result = EC_Normal;
/* iterate over all nodes */
while (cursor.valid())
{
/* check for known element tags */
if (doc.matchNode(cursor, "observer"))
{
DcmItem *ditem = new DcmItem();
if (ditem != NULL)
{
OFString dateTimeString, nameString, orgaString;
DSRCodedEntryValue codeValue;
DSRXMLCursor childCursor = cursor.getChild();
/* iterate over all child nodes */
while (childCursor.valid())
{
/* check for known element tags */
if (doc.matchNode(childCursor, "code"))
{
/* Verifying Observer Code */
codeValue.readXML(doc, childCursor, flags);
}
else if (doc.matchNode(childCursor, "name"))
{
/* Verifying Observer Name */
DSRPNameTreeNode::getValueFromXMLNodeContent(doc, childCursor.getChild(), nameString);
}
else if (doc.matchNode(childCursor, "datetime"))
{
/* Verification DateTime */
DSRDateTimeTreeNode::getValueFromXMLNodeContent(doc, childCursor, dateTimeString);
} else {
/* Verifying Observer Organization */
doc.getStringFromNodeContent(childCursor, orgaString, "organization", OFTrue /*encoding*/, OFFalse /*clearString*/);
}
/* proceed with next node */
childCursor.gotoNext();
}
/* put string values into the sequence item */
putStringValueToDataset(*ditem, DCM_VerificationDateTime, dateTimeString);
putStringValueToDataset(*ditem, DCM_VerifyingObserverName, nameString);
putStringValueToDataset(*ditem, DCM_VerifyingOrganization, orgaString);
/* write code value to sequence item (might be empty, type 2) */
codeValue.writeSequence(*ditem, DCM_VerifyingObserverIdentificationCodeSequence);
/* insert items into sequence */
VerifyingObserver.insert(ditem);
}
} else
doc.printUnexpectedNodeWarning(cursor);
/* proceed with next node */
cursor.gotoNext();
}
}
return result;
}
OFCondition DSRDocument::writeXML(STD_NAMESPACE ostream &stream,
const size_t flags)
{
OFCondition result = SR_EC_InvalidDocument;
/* only write valid documents */
if (isValid())
{
DCMSR_DEBUG("Writing SR document to XML format");
/* used for multiple purposes */
OFString tmpString;
/* update all DICOM attributes */
updateAttributes();
/* check whether general SR modules are used */
const OFBool usesGeneralSRModules = usesSRDocumentGeneralModule(getDocumentType());
// --- XML document structure (start) ---
stream << "" << OFendl;
stream << "" is closed in next writeXML() call
else
stream << "
" << OFendl;
obsCode.writeXML(stream, flags);
stream << "
" << OFendl;
}
writeStringValueToXML(stream, organization, "organization", (flags & XF_writeEmptyTags) > 0);
}
stream << "
Patient: | " << OFendl; stream << ""; renderHTMLPatientData(stream, newFlags); stream << " | " << OFendl; stream << "|
Referring Physician: | " << OFendl; stream << "" << convertToHTMLString(dicomToReadablePersonName(getStringValueFromElement(ReferringPhysicianName, tmpString), string2), htmlString, newFlags); stream << " | " << OFendl; stream << "|
Study: | " << OFendl; stream << "" << convertToHTMLString(getStringValueFromElement(StudyDescription, tmpString), htmlString, newFlags); if (!StudyID.isEmpty()) stream << " (#" << convertToHTMLString(getStringValueFromElement(StudyID, tmpString), htmlString, newFlags) << ")"; stream << " | " << OFendl; stream << "|
Series: | " << OFendl; stream << "" << convertToHTMLString(getStringValueFromElement(SeriesDescription, tmpString), htmlString, newFlags); if (!SeriesNumber.isEmpty()) stream << " (#" << convertToHTMLString(getStringValueFromElement(SeriesNumber, tmpString), htmlString, newFlags) << ")"; stream << " | " << OFendl; stream << "|
Protocol: | " << OFendl; stream << "" << convertToHTMLString(getStringValueFromElement(ProtocolName, tmpString), htmlString, newFlags) << " | " << OFendl; stream << "|
Manufacturer: | " << OFendl; stream << "" << convertToHTMLString(getStringValueFromElement(Manufacturer, tmpString), htmlString, newFlags); OFString deviceStr; if (!ManufacturerModelName.isEmpty()) deviceStr += convertToHTMLString(getStringValueFromElement(ManufacturerModelName, tmpString), htmlString, newFlags); if (!DeviceSerialNumber.isEmpty()) { if (!deviceStr.empty()) deviceStr += ", "; deviceStr += '#'; deviceStr += convertToHTMLString(getStringValueFromElement(DeviceSerialNumber, tmpString), htmlString, newFlags); } if (!deviceStr.empty()) stream << " (" << deviceStr << ")"; stream << " | " << OFendl; stream << "|
Preliminary Flag: | " << OFendl; stream << "" << getStringValueFromElement(PreliminaryFlag, tmpString) << " | " << OFendl; stream << "|
Completion Flag: | " << OFendl; stream << "" << getStringValueFromElement(CompletionFlag, tmpString) << " | " << OFendl; stream << "|
" << OFendl; stream << " | " << convertToHTMLString(getStringValueFromElement(CompletionFlagDescription, tmpString), htmlString, newFlags); stream << " | " << OFendl; stream << "|
Predecessor Docs: | " << OFendl; renderHTMLReferenceList(stream, PredecessorDocuments, flags); stream << "||
Identical Docs: | " << OFendl; renderHTMLReferenceList(stream, IdenticalDocuments, flags); stream << "||
Referenced Objects: | " << OFendl; renderHTMLReferenceList(stream, ReferencedInstances, flags); stream << "||
Verification Flag: | " << OFendl; stream << "" << getStringValueFromElement(VerificationFlag, tmpString) << " | " << OFendl; stream << "|
Verifying Observers: | " << OFendl; else stream << "" << OFendl; stream << " | "; stream << dicomToReadableDateTime(dateTime, string2) << " - "; /* render code details as a tooltip (HTML 4.01 or above) */ if (obsCode.isValid() && (newFlags & DSRTypes::HF_useCodeDetailsTooltip)) { if (newFlags & HF_XHTML11Compatibility) stream << ""; } stream << convertToHTMLString(dicomToReadablePersonName(obsName, string2), htmlString, newFlags); if (obsCode.isValid() && (newFlags & DSRTypes::HF_useCodeDetailsTooltip)) stream << ""; else { /* render code in a conventional manner */ if (obsCode.isValid() && ((newFlags & HF_renderAllCodes) == HF_renderAllCodes)) { stream << " (" << convertToHTMLString(obsCode.getCodeValue(), htmlString, newFlags); stream << ", " << convertToHTMLString(obsCode.getCodingSchemeDesignator(), htmlString, newFlags); if (!obsCode.getCodingSchemeVersion().empty()) stream << " [" << convertToHTMLString(obsCode.getCodingSchemeVersion(), htmlString, newFlags) << "]"; stream << ", "" << convertToHTMLString(obsCode.getCodeMeaning(), htmlString, newFlags) << "")"; } } stream << ", " << convertToHTMLString(organization, htmlString, newFlags); stream << " | " << OFendl; stream << "
Content Date/Time: | " << OFendl; stream << "" << dicomToReadableDate(getStringValueFromElement(ContentDate, tmpString), string2) << " "; stream << dicomToReadableTime(getStringValueFromElement(ContentTime, tmpString), string2) << " | " << OFendl; stream << "