/* * * Copyright (C) 2000-2021, 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: DSRImageReferenceValue * */ #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ #include "dcmtk/dcmsr/dsrimgvl.h" #include "dcmtk/dcmsr/dsrxmld.h" #include "dcmtk/dcmimgle/dcmimage.h" #include "dcmtk/dcmimgle/diutils.h" #include "dcmtk/dcmimage/diregist.h" /* add support for color images */ #include "dcmtk/dcmimage/diquant.h" /* for DcmQuant */ #include "dcmtk/dcmdata/dcdeftag.h" #include "dcmtk/dcmdata/dcuid.h" DSRImageReferenceValue::DSRImageReferenceValue() : DSRCompositeReferenceValue(), FrameList(), SegmentList(), PresentationState(), RealWorldValueMapping(), IconImage(NULL) { } DSRImageReferenceValue::DSRImageReferenceValue(const OFString &sopClassUID, const OFString &sopInstanceUID, const OFBool check) : DSRCompositeReferenceValue(), FrameList(), SegmentList(), PresentationState(), RealWorldValueMapping(), IconImage(NULL) { /* use the set method for checking purposes */ setReference(sopClassUID, sopInstanceUID, check); } DSRImageReferenceValue::DSRImageReferenceValue(const OFString &imageSOPClassUID, const OFString &imageSOPInstanceUID, const OFString &pstateSOPClassUID, const OFString &pstateSOPInstanceUID, const OFBool check) : DSRCompositeReferenceValue(), FrameList(), SegmentList(), PresentationState(), RealWorldValueMapping(), IconImage(NULL) { /* use the set methods for checking purposes */ setReference(imageSOPClassUID, imageSOPInstanceUID, check); setPresentationState(DSRCompositeReferenceValue(pstateSOPClassUID, pstateSOPInstanceUID, OFFalse /*check*/), check); } DSRImageReferenceValue::DSRImageReferenceValue(const DSRImageReferenceValue &referenceValue) : DSRCompositeReferenceValue(referenceValue), FrameList(referenceValue.FrameList), SegmentList(referenceValue.SegmentList), PresentationState(referenceValue.PresentationState), RealWorldValueMapping(referenceValue.RealWorldValueMapping), IconImage(NULL) { /* do not check values since this would be unexpected to the user */ /* create copy of icon image (if any), first frame only */ if (referenceValue.IconImage != NULL) IconImage = referenceValue.IconImage->createDicomImage(0 /*fstart*/, 1 /*fcount*/); } DSRImageReferenceValue::DSRImageReferenceValue(const DSRCompositeReferenceValue &imageReferenceValue, const DSRCompositeReferenceValue &pstateReferenceValue) : DSRCompositeReferenceValue(imageReferenceValue), FrameList(), SegmentList(), PresentationState(pstateReferenceValue), RealWorldValueMapping(), IconImage(NULL) { } DSRImageReferenceValue::~DSRImageReferenceValue() { deleteIconImage(); } DSRImageReferenceValue &DSRImageReferenceValue::operator=(const DSRImageReferenceValue &referenceValue) { /* check for self-assignment, which would create a memory leak */ if (this != &referenceValue) { DSRCompositeReferenceValue::operator=(referenceValue); /* do not check since this would be unexpected to the user */ FrameList = referenceValue.FrameList; SegmentList = referenceValue.SegmentList; PresentationState = referenceValue.PresentationState; RealWorldValueMapping = referenceValue.RealWorldValueMapping; /* create copy of icon image (if any), first frame only */ IconImage = (referenceValue.IconImage != NULL) ? referenceValue.IconImage->createDicomImage(0 /*fstart*/, 1 /*fcount*/) : NULL; } return *this; } OFBool DSRImageReferenceValue::operator==(const DSRImageReferenceValue &referenceValue) const { /* the optional icon image is not used for comparison */ return DSRCompositeReferenceValue::operator==(referenceValue) && (FrameList == referenceValue.FrameList) && (SegmentList == referenceValue.SegmentList) && (PresentationState == referenceValue.PresentationState) && (RealWorldValueMapping == referenceValue.RealWorldValueMapping); } OFBool DSRImageReferenceValue::operator!=(const DSRImageReferenceValue &referenceValue) const { /* the optional icon image is not used for comparison */ return DSRCompositeReferenceValue::operator!=(referenceValue) || (FrameList != referenceValue.FrameList) || (SegmentList != referenceValue.SegmentList) || (PresentationState != referenceValue.PresentationState) || (RealWorldValueMapping != referenceValue.RealWorldValueMapping); } void DSRImageReferenceValue::clear() { DSRCompositeReferenceValue::clear(); FrameList.clear(); SegmentList.clear(); PresentationState.clear(); RealWorldValueMapping.clear(); deleteIconImage(); } OFBool DSRImageReferenceValue::isValid() const { return DSRCompositeReferenceValue::isValid() && checkCurrentValue().good(); } OFBool DSRImageReferenceValue::isShort(const size_t flags) const { return (FrameList.isEmpty() && SegmentList.isEmpty()) || !(flags & DSRTypes::HF_renderFullData); } OFBool DSRImageReferenceValue::isSegmentation() const { return isSegmentationObject(SOPClassUID); } OFCondition DSRImageReferenceValue::print(STD_NAMESPACE ostream &stream, const size_t flags) const { /* first, determine SOP class component */ OFString sopClassString = "\"" + SOPClassUID + "\""; if (!(flags & DSRTypes::PF_printSOPClassUID)) { if (flags & DSRTypes::PF_printLongSOPClassName) { /* look up name of known SOP classes */ const char *className = dcmFindNameOfUID(SOPClassUID.c_str()); if (className != NULL) sopClassString = className; } else { /* create short name for SOP class, e.g. "CT image" */ const char *modality = dcmSOPClassUIDToModality(SOPClassUID.c_str()); if (modality != NULL) sopClassString = OFString(modality) + " image"; } } /* and then, print it */ stream << "(" << sopClassString << ","; /* print SOP instance component (if desired) */ if (flags & DSRTypes::PF_printSOPInstanceUID) stream << "\"" << SOPInstanceUID << "\""; /* print list of frame or segment numbers (if present) */ if (!FrameList.isEmpty()) { stream << ","; FrameList.print(stream, flags); } else if (!SegmentList.isEmpty()) { stream << ","; SegmentList.print(stream, flags); } stream << ")"; /* print information on presentation state (if present) */ if (PresentationState.isValid()) { /* first, determine SOP class component */ OFString pstateClassString = "\"" + PresentationState.getSOPClassUID() + "\""; if (!(flags & DSRTypes::PF_printSOPClassUID)) { if (flags & DSRTypes::PF_printLongSOPClassName) { /* look up name of known SOP classes */ const char *className = dcmFindNameOfUID(PresentationState.getSOPClassUID().c_str()); if (className != NULL) pstateClassString = className; } else { /* create short name for presentation state, e.g. "GSPS" */ const DSRTypes::E_PresentationStateType pstateType = DSRTypes::sopClassUIDToPresentationStateType(PresentationState.getSOPClassUID()); if (pstateType != DSRTypes::PT_invalid) pstateClassString = DSRTypes::presentationStateTypeToShortName(pstateType); } } /* and, then print it */ stream << ",(" << pstateClassString << ","; /* also print SOP instance component (if desired) */ if (flags & DSRTypes::PF_printSOPInstanceUID) stream << "\"" << PresentationState.getSOPInstanceUID() << "\""; stream << ")"; } return EC_Normal; } OFCondition DSRImageReferenceValue::readXML(const DSRXMLDocument &doc, DSRXMLCursor cursor, const size_t flags) { /* first read general composite reference information */ OFCondition result = DSRCompositeReferenceValue::readXML(doc, cursor, flags); /* then read image related XML tags */ if (result.good()) { cursor.gotoChild(); /* either frame or segment list (conditional) */ DSRXMLCursor childCursor = doc.getNamedNode(cursor, "frames", OFFalse /*required*/); if (childCursor.valid()) { OFString tmpString; /* put element content to the frame list */ result = FrameList.putString(doc.getStringFromNodeContent(childCursor, tmpString).c_str()); } else { childCursor = doc.getNamedNode(cursor, "segments", OFFalse /*required*/); if (childCursor.valid()) { OFString tmpString; /* put element content to the segment list */ result = SegmentList.putString(doc.getStringFromNodeContent(childCursor, tmpString).c_str()); } } if (result.good()) { /* presentation state object (optional) */ childCursor = doc.getNamedNode(cursor, "pstate", OFFalse /*required*/); if (childCursor.valid()) result = PresentationState.readXML(doc, childCursor, flags); } if (result.good()) { /* real world value mapping object (optional) */ childCursor = doc.getNamedNode(cursor, "mapping", OFFalse /*required*/); if (childCursor.valid()) result = RealWorldValueMapping.readXML(doc, childCursor, flags); } } return result; } OFCondition DSRImageReferenceValue::writeXML(STD_NAMESPACE ostream &stream, const size_t flags) const { OFCondition result = DSRCompositeReferenceValue::writeXML(stream, flags); /* either frame or segment list (conditional) */ if (((flags & DSRTypes::XF_writeEmptyTags) && SegmentList.isEmpty()) || !FrameList.isEmpty()) { stream << ""; FrameList.print(stream); stream << "" << OFendl; } else if ((flags & DSRTypes::XF_writeEmptyTags) || !SegmentList.isEmpty()) { stream << ""; SegmentList.print(stream); stream << "" << OFendl; } /* presentation state object (optional) */ if ((flags & DSRTypes::XF_writeEmptyTags) || PresentationState.isValid()) { stream << "" << OFendl; if (PresentationState.isValid()) PresentationState.writeXML(stream, flags); stream << "" << OFendl; } /* real world value mapping object (optional) */ if ((flags & DSRTypes::XF_writeEmptyTags) || RealWorldValueMapping.isValid()) { stream << "" << OFendl; if (RealWorldValueMapping.isValid()) RealWorldValueMapping.writeXML(stream, flags); stream << "" << OFendl; } return result; } OFCondition DSRImageReferenceValue::readItem(DcmItem &dataset, const size_t flags) { /* be very careful, delete any previously created icon image (should never apply) */ deleteIconImage(); /* read ReferencedSOPClassUID and ReferencedSOPInstanceUID */ OFCondition result = DSRCompositeReferenceValue::readItem(dataset, flags); if (result.good()) { /* read ReferencedFrameNumber (conditional) */ FrameList.read(dataset, flags); /* read ReferencedSegmentNumber (conditional) */ SegmentList.read(dataset, flags); /* read ReferencedSOPSequence (Presentation State, optional) */ PresentationState.readSequence(dataset, DCM_ReferencedSOPSequence, "3" /*type*/, flags); /* read ReferencedRealWorldValueMappingInstanceSequence (optional) */ RealWorldValueMapping.readSequence(dataset, DCM_ReferencedRealWorldValueMappingInstanceSequence, "3" /*type*/, flags); /* read IconImageSequence (optional) */ DcmSequenceOfItems *dseq = NULL; /* use local status variable since the sequence is optional */ const OFCondition seqStatus = dataset.findAndGetSequence(DCM_IconImageSequence, dseq); DSRTypes::checkElementValue(dseq, DCM_IconImageSequence, "1", "3", seqStatus, "IMAGE content item"); if (seqStatus.good()) { /* check for empty sequence (allowed!) */ if (!dseq->isEmpty()) { /* read first item */ DcmItem *ditem = dseq->getItem(0); if ((ditem != NULL) && !ditem->isEmpty()) { /* try to load/process the icon image */ IconImage = new DicomImage(ditem, EXS_LittleEndianExplicit); if (IconImage != NULL) { if (IconImage->getStatus() != EIS_Normal) result = SR_EC_CannotCreateIconImage; } else result = EC_MemoryExhausted; } else result = SR_EC_InvalidDocumentTree; } } /* check data and report warnings if any */ checkCurrentValue(OFTrue /*reportWarnings*/); } return result; } OFCondition DSRImageReferenceValue::writeItem(DcmItem &dataset) const { /* write ReferencedSOPClassUID and ReferencedSOPInstanceUID */ OFCondition result = DSRCompositeReferenceValue::writeItem(dataset); /* write ReferencedFrameNumber or ReferencedSegmentNumber (conditional) */ if (result.good()) { if (!FrameList.isEmpty()) result = FrameList.write(dataset); else if (!SegmentList.isEmpty()) result = SegmentList.write(dataset); /* write ReferencedSOPSequence (Presentation State, optional) */ if (result.good()) { if (PresentationState.isValid()) result = PresentationState.writeSequence(dataset, DCM_ReferencedSOPSequence); } /* write ReferencedRealWorldValueMappingInstanceSequence (optional) */ if (result.good()) { if (RealWorldValueMapping.isValid()) result = RealWorldValueMapping.writeSequence(dataset, DCM_ReferencedRealWorldValueMappingInstanceSequence); } /* write IconImageSequence (optional) */ if (result.good() && (IconImage != NULL)) { DcmItem *ditem = NULL; /* create sequence with a single item */ result = dataset.findOrCreateSequenceItem(DCM_IconImageSequence, ditem, 0 /*position*/); if (result.good()) { /* monochrome images can be written directly */ if (IconImage->isMonochrome()) { /* write icon image to dataset */ if (IconImage->writeFrameToDataset(*ditem)) { /* delete unwanted element NumberOfFrames (0028,0008) */ ditem->findAndDeleteElement(DCM_NumberOfFrames); } else result = EC_CorruptedData; } else { OFString tmpString; /* color images need to be converted to "PALETTE COLOR" */ result = DcmQuant::createPaletteColorImage(*IconImage, *ditem, OFTrue /*writeAsOW*/, OFFalse /*write16BitEntries*/, OFFalse /*floydSteinberg*/, 256 /*numberOfColors*/, tmpString /*description*/); } } } /* check data and report warnings if any */ checkCurrentValue(OFTrue /*reportWarnings*/); } return result; } OFCondition DSRImageReferenceValue::renderHTML(STD_NAMESPACE ostream &docStream, STD_NAMESPACE ostream &annexStream, size_t &annexNumber, const size_t flags) const { /* reference: image */ docStream << ""; /* text: image */ const char *modality = dcmSOPClassUIDToModality(SOPClassUID.c_str()); if (modality != NULL) docStream << modality; else docStream << "unknown"; docStream << " image"; /* text: presentation state */ if (PresentationState.isValid()) docStream << " with presentation state"; docStream << ""; if (!isShort(flags)) { const char *lineBreak = (flags & DSRTypes::HF_renderSectionTitlesInline) ? " " : (flags & DSRTypes::HF_XHTML11Compatibility) ? "
" : "
"; if (flags & DSRTypes::HF_currentlyInsideAnnex) { docStream << OFendl << "

" << OFendl; /* render frame list (= print)*/ docStream << "Referenced Frame Number:" << lineBreak; FrameList.print(docStream); docStream << "

"; } else { docStream << " "; DSRTypes::createHTMLAnnexEntry(docStream, annexStream, "for more details see", annexNumber, flags); annexStream << "

" << OFendl; /* render frame list (= print)*/ annexStream << "Referenced Frame Number:" << lineBreak; FrameList.print(annexStream); annexStream << "

" << OFendl; } } return EC_Normal; } OFCondition DSRImageReferenceValue::createIconImage(const OFString &filename, const unsigned long frame, const unsigned long width, const unsigned long height) { /* delete old icon image (if any) */ deleteIconImage(); OFCondition result = EC_IllegalParameter; if (!filename.empty()) { /* try to load specified DICOM image */ const unsigned long flags = CIF_UsePartialAccessToPixelData | CIF_NeverAccessEmbeddedOverlays; DicomImage *image = new DicomImage(filename.c_str(), flags, frame, 1 /*fcount*/); if (image != NULL) { /* set VOI window (for monochrome images) */ if (image->isMonochrome() && !image->setWindow(0)) image->setMinMaxWindow(); /* do the real work: create a down-scaled version of the DICOM image */ result = createIconImage(image, width, height); delete image; } else result = EC_MemoryExhausted; } return result; } OFCondition DSRImageReferenceValue::createIconImage(DcmObject *object, const E_TransferSyntax xfer, const unsigned long frame, const unsigned long width, const unsigned long height) { /* delete old icon image (if any) */ deleteIconImage(); OFCondition result = EC_IllegalParameter; if (object != NULL) { /* try to load specified DICOM image */ const unsigned long flags = CIF_UsePartialAccessToPixelData | CIF_NeverAccessEmbeddedOverlays; DicomImage *image = new DicomImage(object, xfer, flags, frame, 1 /*fcount*/); if (image != NULL) { /* set VOI window (for monochrome images) */ if (image->isMonochrome() && !image->setWindow(0)) image->setMinMaxWindow(); /* do the real work: create a down-scaled version of the DICOM image */ result = createIconImage(image, width, height); delete image; } else result = EC_MemoryExhausted; } return result; } OFCondition DSRImageReferenceValue::createIconImage(const DicomImage *image, const unsigned long width, const unsigned long height) { /* delete old icon image (if any) */ deleteIconImage(); OFCondition result = EC_IllegalParameter; if (image != NULL) { const EI_Status imageStatus = image->getStatus(); /* check whether image loading/processing was successful */ switch (imageStatus) { case EIS_Normal: { if (image->getFrameCount() > 1) DCMSR_DEBUG("DICOM image passed for creating an icon image contains multiple frames"); /* create a down-scaled version of the DICOM image */ const int aspect = (width == 0) || (height == 0); IconImage = image->createScaledImage(width, height, 1 /*interpolate*/, aspect); result = (IconImage != NULL) ? EC_Normal : SR_EC_CannotCreateIconImage; break; } case EIS_InvalidDocument: case EIS_InvalidImage: result = SR_EC_InvalidDocument; break; case EIS_MissingAttribute: result = SR_EC_MandatoryAttributeMissing; break; case EIS_InvalidValue: result = SR_EC_InvalidValue; break; case EIS_NotSupportedValue: result = SR_EC_UnsupportedValue; break; case EIS_MemoryFailure: result = EC_MemoryExhausted; break; default: /* this is the fallback for all other kind of errors */ result = SR_EC_CannotCreateIconImage; break; } } return result; } void DSRImageReferenceValue::deleteIconImage() { delete IconImage; IconImage = NULL; } OFCondition DSRImageReferenceValue::getValue(DSRImageReferenceValue &referenceValue) const { referenceValue = *this; return EC_Normal; } OFCondition DSRImageReferenceValue::setValue(const DSRImageReferenceValue &referenceValue, const OFBool check) { OFCondition result = DSRCompositeReferenceValue::setValue(referenceValue, check); if (result.good()) { FrameList = referenceValue.FrameList; SegmentList = referenceValue.SegmentList; /* ignore status (return value) since the references are optional */ setPresentationState(referenceValue.PresentationState, check); setRealWorldValueMapping(referenceValue.RealWorldValueMapping, check); } return result; } OFCondition DSRImageReferenceValue::setPresentationState(const DSRCompositeReferenceValue &pstateValue, const OFBool check) { OFCondition result = EC_Normal; /* check whether the passed value is valid */ if (check) result = checkPresentationState(pstateValue); /* both UID values need to be empty or non-empty (optional) */ else if (pstateValue.getSOPClassUID().empty() != pstateValue.getSOPInstanceUID().empty()) result = SR_EC_InvalidValue; if (result.good()) PresentationState = pstateValue; return result; } OFCondition DSRImageReferenceValue::setRealWorldValueMapping(const DSRCompositeReferenceValue &mappingValue, const OFBool check) { OFCondition result = EC_Normal; /* check whether the passed value is valid */ if (check) result = checkRealWorldValueMapping(mappingValue); /* both UID values need to be empty or non-empty (optional) */ else if (mappingValue.getSOPClassUID().empty() != mappingValue.getSOPInstanceUID().empty()) result = SR_EC_InvalidValue; if (result.good()) RealWorldValueMapping = mappingValue; return result; } OFBool DSRImageReferenceValue::appliesToFrame(const Sint32 frameNumber) const { OFBool result = OFTrue; if (!FrameList.isEmpty()) result = FrameList.isElement(frameNumber); return result; } OFBool DSRImageReferenceValue::appliesToSegment(const Uint16 segmentNumber) const { OFBool result = OFTrue; if (!SegmentList.isEmpty()) result = SegmentList.isElement(segmentNumber); return result; } OFBool DSRImageReferenceValue::isSegmentationObject(const OFString &sopClassUID) const { /* check for all segmentation SOP classes (according to DICOM PS 3.6-2020c) */ return (sopClassUID == UID_SegmentationStorage) || (sopClassUID == UID_SurfaceSegmentationStorage); } // helper macro to avoid annoying check of boolean flag #define REPORT_WARNING(msg) { if (reportWarnings) DCMSR_WARN(msg); } OFCondition DSRImageReferenceValue::checkSOPClassUID(const OFString &sopClassUID, const OFBool reportWarnings) const { OFCondition result = DSRCompositeReferenceValue::checkSOPClassUID(sopClassUID); if (result.good()) { /* check for all valid/known SOP classes (according to DICOM PS 3.6) */ if (!dcmIsImageStorageSOPClassUID(sopClassUID.c_str()) && !isSegmentationObject(sopClassUID)) { REPORT_WARNING("Invalid or unknown image SOP class referenced from IMAGE content item") result = SR_EC_InvalidValue; } } return result; } OFCondition DSRImageReferenceValue::checkPresentationState(const DSRCompositeReferenceValue &referenceValue, const OFBool reportWarnings) const { OFCondition result = EC_Normal; /* the reference to a presentation state object is optional, so an empty value is also valid */ if (!referenceValue.isEmpty()) { if (DSRTypes::sopClassUIDToPresentationStateType(referenceValue.getSOPClassUID()) == DSRTypes::PT_invalid) { REPORT_WARNING("Invalid or unknown presentation state SOP class referenced from IMAGE content item") result = SR_EC_InvalidValue; } } return result; } OFCondition DSRImageReferenceValue::checkRealWorldValueMapping(const DSRCompositeReferenceValue &referenceValue, const OFBool reportWarnings) const { OFCondition result = EC_Normal; /* the reference to a real world value mapping object is optional, so an empty value is also valid */ if (!referenceValue.isEmpty()) { if (referenceValue.getSOPClassUID() != UID_RealWorldValueMappingStorage) { REPORT_WARNING("Invalid or unknown real world value mapping SOP class referenced from IMAGE content item") result = SR_EC_InvalidValue; } } return result; } OFCondition DSRImageReferenceValue::checkListData(const OFString &sopClassUID, const DSRImageFrameList &frameList, const DSRImageSegmentList &segmentList, const OFBool reportWarnings) const { OFCondition result = EC_Normal; /* check whether both lists of referenced frame and segment numbers are non-empty */ if (!frameList.isEmpty() && !segmentList.isEmpty()) { /* this is just a warning since only one list will ever be written */ REPORT_WARNING("Both Referenced Frame Number and Referenced Segment Number present in IMAGE content item") } /* check whether referenced image is a segmentation object (see "type 1C" condition) */ if (!segmentList.isEmpty() && !isSegmentationObject(sopClassUID)) { REPORT_WARNING("Referenced Segment Number present in IMAGE content item for non-segmentation object") result = SR_EC_InvalidValue; } /* tbd: check whether referenced image is a multi-frame image? (see "type 1C" condition) */ return result; } OFCondition DSRImageReferenceValue::checkCurrentValue(const OFBool reportWarnings) const { OFCondition result = DSRCompositeReferenceValue::checkCurrentValue(reportWarnings); if (result.good()) result = checkPresentationState(PresentationState, reportWarnings); if (result.good()) result = checkRealWorldValueMapping(RealWorldValueMapping, reportWarnings); if (result.good()) result = checkListData(SOPClassUID, FrameList, SegmentList, reportWarnings); return result; }