/* * Copyright (C) 2015-2022, Open Connections GmbH * * All rights reserved. See COPYRIGHT file for details. * * This software and supporting documentation are maintained by * * OFFIS e.V. * R&D Division Health * Escherweg 2 * D-26121 Oldenburg, Germany * * * Module: dcmseg * * Author: Michael Onken * * Purpose: Class representing a Segmentation object * */ #include "dcmtk/config/osconfig.h" #include "dcmtk/dcmdata/dcuid.h" #include "dcmtk/dcmfg/concatenationcreator.h" #include "dcmtk/dcmfg/fgderimg.h" #include "dcmtk/dcmfg/fgfact.h" #include "dcmtk/dcmfg/fgfracon.h" #include "dcmtk/dcmfg/fgplanor.h" #include "dcmtk/dcmfg/fgplanpo.h" #include "dcmtk/dcmfg/fgseg.h" #include "dcmtk/dcmiod/iodutil.h" #include "dcmtk/dcmseg/segdoc.h" #include "dcmtk/dcmseg/segment.h" #include "dcmtk/dcmseg/segutils.h" // default constructor (protected, instance creation via create() function) DcmSegmentation::DcmSegmentation() : DcmSegmentation::IODImage(OFin_place >) , m_SegmentationSeries(DcmSegmentation::IODImage::getData(), DcmSegmentation::IODImage::getRules()) , m_EnhancedGeneralEquipmentModule(DcmSegmentation::IODImage::getData(), DcmSegmentation::IODImage::getRules()) , m_FG(DcmSegmentation::IODImage::getData(), DcmSegmentation::IODImage::getRules()) , m_DimensionModule(DcmSegmentation::IODImage::getData(), DcmSegmentation::IODImage::getRules()) , m_Frames() , m_ImageType("DERIVED\\PRIMARY") , m_ContentIdentificationMacro() , m_SegmentationType(DcmSegTypes::ST_BINARY) , m_SegmentationFractionalType(DcmSegTypes::SFT_OCCUPANCY) , m_MaximumFractionalValue(DCM_MaximumFractionalValue) , m_Segments() , m_FGInterface() { DcmSegmentation::initIODRules(); } void DcmSegmentation::initIODRules() { // ------------ Segmentation Image Module ------------- // Partly overrides rules from General Image Module getRules()->addRule(new IODRule(DCM_ImageType, "2", "1", "SegmentationImageModule", DcmIODTypes::IE_IMAGE), OFTrue); getRules()->addRule(new IODRule(DCM_SegmentationType, "1", "1", "SegmentationImageModule", DcmIODTypes::IE_IMAGE), OFTrue); getRules()->addRule( new IODRule(DCM_SegmentationFractionalType, "1", "1C", "SegmentationImageModule", DcmIODTypes::IE_IMAGE), OFTrue); getRules()->addRule( new IODRule(DCM_MaximumFractionalValue, "1", "1C", "SegmentationImageModule", DcmIODTypes::IE_IMAGE), OFTrue); // Re-use General Image Module instead of Segmentation Image Module getRules()->addRule(new IODRule(DCM_LossyImageCompression, "1", "1", "GeneralImageModule", DcmIODTypes::IE_IMAGE), OFTrue); getRules()->addRule( new IODRule(DCM_LossyImageCompressionMethod, "1-n", "1C", "GeneralImageModule", DcmIODTypes::IE_IMAGE), OFTrue); getRules()->addRule( new IODRule(DCM_LossyImageCompressionRatio, "1-n", "1C", "GeneralImageModule", DcmIODTypes::IE_IMAGE), OFTrue); // Override rule from General Series Module getRules()->addRule(new IODRule(DCM_ReferencedPerformedProcedureStepSequence, "1", "1C", "SegmentationSeriesModule", DcmIODTypes::IE_SERIES), OFTrue); getRules()->addRule(new IODRule(DCM_SeriesNumber, "1", "1", "SegmentationSeriesModule", DcmIODTypes::IE_SERIES), OFTrue); // Instance Number is also used within Content Identification Macro, disable it there m_ContentIdentificationMacro.getIODRules().deleteRule(DCM_InstanceNumber); } DcmSegmentation::~DcmSegmentation() { clearData(); } // static method for loading segmentation objects OFCondition DcmSegmentation::loadFile(const OFString& filename, DcmSegmentation*& segmentation) { DcmFileFormat dcmff; DcmDataset* dataset = NULL; OFCondition result = loadFile(dcmff, filename, dataset); if (result.bad()) return result; return loadDataset(*dataset, segmentation); } // static method for loading segmentation objects OFCondition DcmSegmentation::loadDataset(DcmDataset& dataset, DcmSegmentation*& segmentation) { segmentation = NULL; OFCondition result = DcmSegmentation::decompress(dataset); if (result.bad()) return result; DcmSegmentation* temp = new DcmSegmentation(); if (temp == NULL) { return EC_MemoryExhausted; } result = temp->read(dataset); if (result.good()) { segmentation = temp; } else { delete segmentation; } return result; } OFCondition DcmSegmentation::loadConcatenation(ConcatenationLoader& cl, const OFString& concatenationUID, DcmSegmentation*& segmentation) { DcmDataset dset; segmentation = NULL; OFVector frames; OFCondition result = cl.load(concatenationUID, &dset, frames); if (result.good()) { segmentation = new DcmSegmentation(); if (segmentation) { result = segmentation->readWithoutPixelData(dset); if (result.good()) { segmentation->m_Frames = frames; } } else { result = EC_MemoryExhausted; } } if (result.bad()) { DcmIODUtil::freeContainer(frames); delete segmentation; } return result; } OFCondition DcmSegmentation::createBinarySegmentation(DcmSegmentation*& segmentation, const Uint16 rows, const Uint16 columns, const IODGeneralEquipmentModule::EquipmentInfo& equipmentInfo, const ContentIdentificationMacro& contentIdentification) { OFCondition result = createCommon(segmentation, rows, columns, equipmentInfo, contentIdentification); if (result.bad()) return result; segmentation->m_SegmentationType = DcmSegTypes::ST_BINARY; return result; } OFCondition DcmSegmentation::createFractionalSegmentation(DcmSegmentation*& segmentation, const Uint16 rows, const Uint16 columns, const DcmSegTypes::E_SegmentationFractionalType fractType, const Uint16& maxFractionalValue, const IODGeneralEquipmentModule::EquipmentInfo& equipmentInfo, const ContentIdentificationMacro& contentIdentification) { OFCondition result = createCommon(segmentation, rows, columns, equipmentInfo, contentIdentification); if (result.bad()) return result; segmentation->m_SegmentationType = DcmSegTypes::ST_FRACTIONAL; segmentation->m_SegmentationFractionalType = fractType; segmentation->m_MaximumFractionalValue.putUint16(maxFractionalValue); return result; } OFCondition DcmSegmentation::createCommon(DcmSegmentation*& segmentation, const Uint16 rows, const Uint16 columns, const IODGeneralEquipmentModule::EquipmentInfo& equipmentInfo, const ContentIdentificationMacro& contentIdentification) { if ((rows == 0) || (columns == 0)) { DCMSEG_ERROR("Segmentation must have at least 1 row and 1 column"); return EC_IllegalParameter; } segmentation = new DcmSegmentation(); if (segmentation == NULL) return EC_MemoryExhausted; segmentation->getImagePixel().setRows(rows); segmentation->getImagePixel().setColumns(columns); OFCondition result = segmentation->setContentIdentification(contentIdentification); if (result.good()) { OFString tempstr; contentIdentification.getInstanceNumber(tempstr); result = segmentation->getGeneralImage().setInstanceNumber(tempstr); if (result.bad()) { delete segmentation; segmentation = NULL; return EC_InvalidValue; } DcmIODUtil::setContentDateAndTimeNow(segmentation->getGeneralImage()); result = segmentation->setEquipmentInfo(equipmentInfo, OFTrue /* check */); } if (result.bad()) { delete segmentation; segmentation = NULL; } return result; } FGDerivationImage* DcmSegmentation::createDerivationImageFG(const OFVector& derivationImages, const OFString& derivationDescription) { CodeSequenceMacro derivationCode("113076", "DCM", "Segmentation"); CodeSequenceMacro purpose("121322", "DCM", "Source Image for Image Processing Operation"); return FGDerivationImage::createMinimal(derivationImages, derivationDescription, derivationCode, purpose); } OFCondition DcmSegmentation::read(DcmItem& dataset) { OFCondition result = readWithoutPixelData(dataset); if (result.good()) result = readFrames(dataset); return result; } OFCondition DcmSegmentation::readWithoutPixelData(DcmItem& dataset) { OFString sopClass; if (DcmIODUtil::checkSOPClass(&dataset, UID_SegmentationStorage, sopClass).bad()) { DCMSEG_ERROR("Given file does not seem to be a segmentation storage object since SOP class is: " << sopClass); return IOD_EC_WrongSOPClass; } // Read attributes in base classes DcmSegmentation::IODImage::read(dataset); // Read Segmentation Series Module m_SegmentationSeries.read(dataset); // Read Enhanced General Equipment (i.e. make sure all type 1 elements are // there, which is not checked in General Equipment Module being part of // DcmIODImage. m_EnhancedGeneralEquipmentModule.read(dataset); // Read functional groups module m_FG.read(dataset); // Read functional groups itself m_FGInterface.read(dataset); // Read dimension information m_DimensionModule.read(dataset); readSegmentationType(dataset); readSegments(dataset); readSegmentationFractionalType(dataset); m_ContentIdentificationMacro.read(dataset); // Read specific segmentation elements DcmIODUtil::getAndCheckElementFromDataset( dataset, m_MaximumFractionalValue, getRules()->getByTag(DCM_MaximumFractionalValue)); return EC_Normal; } void DcmSegmentation::setCheckFGOnWrite(const OFBool doCheck) { m_FGInterface.setCheckOnWrite(doCheck); } OFBool DcmSegmentation::getCheckFGOnWrite() { return m_FGInterface.getCheckOnWrite(); } void DcmSegmentation::setCheckDimensionsOnWrite(const OFBool doCheck) { m_DimensionModule.setCheckOnWrite(doCheck); } OFBool DcmSegmentation::getCheckDimensionsOnWrite() { return m_DimensionModule.getCheckOnWrite(); } OFCondition DcmSegmentation::writeWithSeparatePixelData(DcmItem& dataset, Uint8*& pixData, size_t& pixDataLength) { // FGInterface::write() will know whether it has to check FG structure // so we do not need to check FG structure here (OFFalse). if (!check(OFFalse)) { return IOD_EC_InvalidObject; } OFCondition result; // -- Set constant default values written by external modules -- getGeneralImage().setLossyImageCompression("00"); getGeneralImage().setImageType(m_ImageType); getSOPCommon().setSOPClassUID(UID_SegmentationStorage); // -- Extra Study level data -- // Enhanced Equipment Module if (result.good()) result = m_EnhancedGeneralEquipmentModule.write(dataset); // -- Extra Series level data -- // Write segmentation-specific series level attribute (Segmentation Series Module) if (result.good()) result = m_SegmentationSeries.write(dataset); // -- Extra Image level data -- // Write Multi-Frame Functional Groups Module if (result.good()) result = writeMultiFrameFunctionalGroupsModule(dataset); // Write Multi-Frame Dimension Module if (result.good()) result = writeMultiFrameDimensionModule(dataset); // Write segmentation image module and image pixel module if (result.good()) result = writeSegmentationImageModule(dataset); // -- Write common multi frame image IOD attributes -- // Patient Module // General Study Module // General Series Module // Frame of Reference Module // General Equipment Module // General Image Module // Multi-frame Functional Groups Module (except functional groups itself) // SOP Common Module // Common Instance Reference Module if (result.good()) result = DcmSegmentation::IODImage::write(dataset); // Write frame pixel data if (result.good()) { Uint32 numFrames = DcmIODUtil::limitMaxFrames( m_Frames.size(), "More than 2147483647 frames provided, will only write 2147483647"); Uint16 rows, cols; rows = cols = 0; getImagePixel().getRows(rows); getImagePixel().getColumns(cols); result = getTotalBytesRequired(rows, cols, numFrames, pixDataLength); if (result.bad()) return result; pixData = new Uint8[pixDataLength]; if (!pixData) return EC_MemoryExhausted; if (m_SegmentationType == DcmSegTypes::ST_BINARY) result = writeBinaryFrames(pixData, rows, cols, pixDataLength); else if (m_SegmentationType == DcmSegTypes::ST_FRACTIONAL) result = writeFractionalFrames(pixData); else result = SG_EC_UnknownSegmentationType; if (result.bad()) { delete[] pixData; } } return result; } FGInterface& DcmSegmentation::getFunctionalGroups() { return m_FGInterface; } IODMultiFrameFGModule::ConcatenationInfo& DcmSegmentation::getConcatenationInfo() { return m_FG.getConcatenationInfo(); } size_t DcmSegmentation::getNumberOfFrames() { return m_FGInterface.getNumberOfFrames(); } size_t DcmSegmentation::getNumberOfSegments() { return m_Segments.size(); } IODGeneralEquipmentModule& DcmSegmentation::getEquipment() { return DcmSegmentation::IODImage::getEquipment(); } IODSegmentationSeriesModule& DcmSegmentation::getSegmentationSeriesModule() { return m_SegmentationSeries; } OFCondition DcmSegmentation::addSegment(DcmSegment* seg, Uint16& segmentNumber) { segmentNumber = 0; if (seg == NULL) return EC_IllegalParameter; if (m_Segments.size() >= DCM_SEG_MAX_SEGMENTS) { return SG_EC_MaxSegmentsReached; } // Casting is safe since we made sure number of segments fits into 16 bit segmentNumber = OFstatic_cast(Uint16, m_Segments.size() + 1); m_Segments.push_back(seg); return EC_Normal; } OFCondition DcmSegmentation::addFrame(Uint8* pixData) { if (m_Frames.size() >= DCM_SEG_MAX_FRAMES) return SG_EC_MaxFramesReached; OFCondition result; Uint16 rows = 0; Uint16 cols = 0; if (getImagePixel().getRows(rows).good() && getImagePixel().getColumns(cols).good()) { DcmIODTypes::Frame* frame = NULL; if (m_SegmentationType == DcmSegTypes::ST_BINARY) { frame = DcmSegUtils::packBinaryFrame(pixData, rows, cols); if (!frame) { result = IOD_EC_CannotInsertFrame; } } else // fractional { frame = new DcmIODTypes::Frame(); if (frame) { frame->length = rows * cols; frame->pixData = new Uint8[frame->length]; if (frame->pixData) { memcpy(frame->pixData, pixData, frame->length); } else { delete frame; result = EC_MemoryExhausted; } } else result = EC_MemoryExhausted; } if (result.good()) { m_Frames.push_back(frame); } } else { DCMSEG_ERROR("Cannot add frame since rows and/or columns are unknown"); result = IOD_EC_CannotInsertFrame; } return result; } SOPInstanceReferenceMacro& DcmSegmentation::getReferencedPPS() { return getSeries().getReferencedPPS(); } const DcmIODTypes::Frame* DcmSegmentation::getFrame(const size_t& frameNo) { if (frameNo > m_Frames.size() - 1) { return NULL; } return m_Frames[frameNo]; } void DcmSegmentation::getFramesForSegment(const size_t& segmentNumber, OFVector& frameNumbers) { size_t numFrames = getNumberOfFrames(); for (size_t count = 0; count < numFrames; count++) { FGSegmentation* fg = OFstatic_cast( FGSegmentation*, m_FGInterface.get(OFstatic_cast(Uint32, count), DcmFGTypes::EFG_SEGMENTATION)); if (fg == NULL) { DCMSEG_ERROR("Cannot get segmentation functional group for frame " << count); return; } Uint16 refSeg; if (fg->getReferencedSegmentNumber(refSeg).good()) { if (OFstatic_cast(size_t, refSeg) == segmentNumber) { frameNumbers.push_back(count); } } } } OFCondition DcmSegmentation::addForAllFrames(const FGBase& group) { return m_FGInterface.addShared(group); } OFCondition DcmSegmentation::addFrame(Uint8* pixData, const Uint16 segmentNumber, const OFVector& perFrameInformation) { if (m_Frames.size() >= DCM_SEG_MAX_FRAMES) return SG_EC_MaxFramesReached; Uint32 frameNo = OFstatic_cast(Uint32, m_Frames.size()); // will be the index of the frame (counted from 0) OFCondition result; // Check input parameters if (pixData == NULL) { DCMSEG_ERROR("No pixel data provided or zero length"); result = EC_IllegalParameter; } if (segmentNumber > m_Segments.size()) { DCMSEG_ERROR("Cannot add frame: Segment with given number " << segmentNumber << " does not exist"); result = SG_EC_NoSuchSegment; } if (result.bad()) return result; OFVector::const_iterator it = perFrameInformation.begin(); while (it != perFrameInformation.end()) { result = (*it)->check(); if (result.bad()) { DCMSEG_ERROR("Could not add new frame since functional group of type: " << (*it)->getType() << " is invalid: " << result.text()); break; } result = m_FGInterface.addPerFrame(frameNo, *(*it)); if (result.bad()) { DCMSEG_ERROR("Could not add new frame since functional group of type " << (*it)->getType() << ": " << result.text()); break; } it++; } // Now also add Segmentation Functional Group if (result.good()) { FGSegmentation seg; result = seg.setReferencedSegmentNumber(segmentNumber); if (result.good()) { result = m_FGInterface.addPerFrame(frameNo, seg); } else { DCMSEG_ERROR("Could not add new frame, invalid segment number " << segmentNumber << ": " << result.text()); } } // Insert pixel data if (result.good()) { result = addFrame(pixData); } // Cleanup any per-frame groups that might have been inserted and return if (result.bad()) { for (OFVector::const_iterator it2 = perFrameInformation.begin(); it2 != perFrameInformation.end(); it2++) { m_FGInterface.deletePerFrame(frameNo, (*it2)->getType()); } } return result; } ContentIdentificationMacro& DcmSegmentation::getContentIdentification() { return m_ContentIdentificationMacro; } IODMultiframeDimensionModule& DcmSegmentation::getDimensions() { return m_DimensionModule; } OFCondition DcmSegmentation::setLossyImageCompressionFlag(const OFString& ratios, const OFString& methods, const OFBool checkValues) { OFCondition result = getGeneralImage().setLossyImageCompression("01"); if (result.good() || !checkValues) result = getGeneralImage().setLossyImageCompressionMethod(methods); if (result.good() || !checkValues) result = getGeneralImage().setLossyImageCompressionRatio(ratios); if (checkValues) return result; else return EC_Normal; } OFCondition DcmSegmentation::saveFile(const OFString& filename, const E_TransferSyntax writeXfer) { if ((writeXfer != EXS_LittleEndianExplicit) && (writeXfer != EXS_BigEndianExplicit) && (writeXfer != EXS_LittleEndianImplicit) #ifdef WITH_ZLIB && (writeXfer != EXS_DeflatedLittleEndianExplicit) #endif ) { DcmXfer ts(writeXfer); #ifdef WITH_ZLIB DCMSEG_ERROR("Cannot write transfer syntax: " << ts.getXferName() << ": Can only write uncompressed or Deflated)"); #else if (writeXfer == EXS_DeflatedLittleEndianExplicit) { DCMSEG_ERROR("Cannot write transfer syntax: " << ts.getXferName() << ": Deflate (ZLIB) support disabled, can only write uncompressed"); } #endif return EC_CannotChangeRepresentation; } DcmFileFormat dcmff; OFCondition result = writeDataset(*(dcmff.getDataset())); if (result.good()) { result = dcmff.saveFile(filename.c_str(), writeXfer); } if (result.bad()) { DCMSEG_ERROR("Cannot save segmentation document to file " << filename << ": " << result.text()); } return result; } /* -- Setter for DICOM attributes -- */ OFCondition DcmSegmentation::setEquipmentInfo(const IODGeneralEquipmentModule::EquipmentInfo& equipmentInfo, const OFBool checkValue) { if (checkValue) { if (equipmentInfo.m_Manufacturer.empty() || equipmentInfo.m_ManufacturerModelName.empty() || equipmentInfo.m_DeviceSerialNumber.empty() || equipmentInfo.m_SoftwareVersions.empty()) { return EC_InvalidValue; } } OFCondition result = getEquipment().setManufacturer(equipmentInfo.m_Manufacturer, checkValue); if (result.good()) result = getEquipment().setManufacturerModelName(equipmentInfo.m_ManufacturerModelName, checkValue); if (result.good()) result = getEquipment().setDeviceSerialNumber(equipmentInfo.m_DeviceSerialNumber, checkValue); if (result.good()) result = getEquipment().setSoftwareVersions(equipmentInfo.m_SoftwareVersions, checkValue); return result; } OFCondition DcmSegmentation::setContentIdentification(const ContentIdentificationMacro& contentIdentification, const OFBool checkValue) { // Instance Number and Content Label must be filled out, rest can be empty OFCondition result; if (checkValue) { result = OFconst_cast(ContentIdentificationMacro*, &contentIdentification)->check(); } if (result.bad()) return result; m_ContentIdentificationMacro = contentIdentification; return result; } /* -- Getter for DICOM attributes -- */ DcmSegment* DcmSegmentation::getSegment(const size_t segmentNumber) { // check for invalid index if ((segmentNumber == 0) || (segmentNumber > m_Segments.size())) { return NULL; } // logical segment numbering starts with 1, so subtract 1 for vector index return m_Segments[segmentNumber - 1]; } OFBool DcmSegmentation::getSegmentNumber(const DcmSegment* segment, size_t& segmentNumber) { segmentNumber = 0; size_t max = m_Segments.size(); for (size_t count = 0; count < max; count++) { if (m_Segments.at(count) == segment) { segmentNumber = OFstatic_cast(Uint16, count + 1); return OFTrue; } } // not found return OFFalse; } OFCondition DcmSegmentation::getModality(OFString& value, const long signed int pos) const { (void)pos; // Fixed for Segmentations to value "SEG" value = "SEG"; return EC_Normal; } OFCondition DcmSegmentation::importFromSourceImage(const OFString& filename, const bool takeOverCharset) { DcmFileFormat dcmff; OFCondition result = dcmff.loadFile(filename); if (result.good()) { return importFromSourceImage(*(dcmff.getDataset()), takeOverCharset); } return result; } OFCondition DcmSegmentation::importFromSourceImage(DcmItem& dataset, const bool takeOverCharset) { OFString FoR; dataset.findAndGetOFStringArray(DCM_FrameOfReferenceUID, FoR); return DcmIODCommon::importHierarchy(dataset, OFTrue, // Patient OFTrue, // Study !FoR.empty(), // Frame of Reference OFFalse, // Series takeOverCharset); } /* protected functions */ OFCondition DcmSegmentation::writeSegments(DcmItem& item) { OFCondition result; DcmIODUtil::writeSubSequence >( result, DCM_SegmentSequence, m_Segments, item, "1-n", "1", "SegmentationImageModule"); return result; } OFCondition DcmSegmentation::readSegments(DcmItem& item) { return DcmIODUtil::readSubSequence >( item, DCM_SegmentSequence, m_Segments, "1-n", "1", "SegmentationImageModule"); } OFCondition DcmSegmentation::readFrames(DcmItem& dataset) { OFCondition result; Uint16 allocated, stored, high, spp, pixelRep, rows, cols; Uint32 numberOfFrames = 0; allocated = stored = high = spp = rows = cols = 0; pixelRep = 2; // invalid value for this attribute OFString colorModel; /* check the typical image pixel attributes and get correct(ed) values */ result = getAndCheckImagePixelAttributes( dataset, allocated, stored, high, spp, pixelRep, rows, cols, numberOfFrames, colorModel); if (result.bad()) return result; /* Check length of pixel data element */ DcmElement* pixelData = NULL; if (dataset.findAndGetElement(DCM_PixelData, pixelData).bad()) return IOD_EC_InvalidPixelData; if (!checkPixDataLength(pixelData, rows, cols, numberOfFrames)) return IOD_EC_InvalidPixelData; /* Get pixel data values */ Uint8* pixels = NULL; result = pixelData->getUint8Array(pixels); if (result.bad()) { DCMSEG_ERROR("Cannot read pixel data"); return result; } /* Read all frames into dedicated data structure */ size_t pixelsPerFrame = OFstatic_cast(size_t, rows) * cols; if (m_SegmentationType == DcmSegTypes::ST_BINARY) { result = DcmIODUtil::extractBinaryFrames(pixels, numberOfFrames, pixelsPerFrame, m_Frames); if (result.bad()) { return result; } } else if (m_SegmentationType == DcmSegTypes::ST_FRACTIONAL) { for (size_t count = 0; count < numberOfFrames; count++) { DcmIODTypes::Frame* frame = new DcmIODTypes::Frame(); if (!frame) return EC_MemoryExhausted; frame->length = pixelsPerFrame; frame->pixData = new Uint8[pixelsPerFrame]; if (!frame->pixData) { delete frame; return EC_MemoryExhausted; } memcpy(frame->pixData, pixels + count * pixelsPerFrame, pixelsPerFrame); m_Frames.push_back(frame); } } return result; } OFCondition DcmSegmentation::getAndCheckImagePixelAttributes(DcmItem& dataset, Uint16& allocated, Uint16& stored, Uint16& high, Uint16& spp, Uint16& pixelRep, Uint16& rows, Uint16& cols, Uint32& numberOfFrames, OFString& colorModel) { OFBool fail = OFFalse; dataset.findAndGetUint16(DCM_BitsAllocated, allocated); dataset.findAndGetUint16(DCM_BitsStored, stored); dataset.findAndGetUint16(DCM_HighBit, high); dataset.findAndGetUint16(DCM_PixelRepresentation, pixelRep); dataset.findAndGetUint16(DCM_SamplesPerPixel, spp); dataset.findAndGetOFStringArray(DCM_PhotometricInterpretation, colorModel); /* Rows and Columns */ OFCondition result = getImagePixel().getRows(rows); if (result.good()) result = getImagePixel().getColumns(cols); if (result.bad()) { DCMSEG_ERROR("Cannot find Rows or Columns in dataset"); fail = OFTrue; } /* Number of Frames */ Sint32 numFrames = 0; result = m_FG.getNumberOfFrames(numFrames); if (result.bad()) { DCMSEG_ERROR("Number of Frames not set"); fail = OFTrue; } else { if (numFrames < 0) { DCMSEG_ERROR("Number of Frames must be greater than 0"); fail = OFTrue; } else { numberOfFrames = OFstatic_cast(Uint32, numFrames); } } Uint16 depth = 0; if (m_SegmentationType == DcmSegTypes::ST_BINARY) depth = 1; else depth = 8; if (allocated != depth) { DCMSEG_WARN("Bits Allocated is not set correctly (" << allocated << ", ignored), assuming value " << depth << " as required for " << DcmSegTypes::segtype2OFString(m_SegmentationType) << " segmentation objects"); allocated = depth; } if (stored != depth) { DCMSEG_WARN("Bits Stored is not set correctly (" << stored << ", ignored), assuming value " << depth << " as required for " << DcmSegTypes::segtype2OFString(m_SegmentationType) << " segmentation objects"); stored = depth; } if (high != depth - 1) { DCMSEG_WARN("High Bit is not set correctly (" << high << ", ignored), assuming value " << depth - 1 << " as required for " << DcmSegTypes::segtype2OFString(m_SegmentationType) << " segmentation objects"); high = depth - 1; } if (spp != 1) { DCMSEG_WARN("Samples per Pixel is not set correctly (" << spp << ", ignored), assuming value 1 as required for segmentation objects"); spp = 1; } if (pixelRep != 0) { DCMSEG_WARN("Pixel Representation is not set correctly (" << pixelRep << ", ignored), assuming value 0 as required for segmentation objects"); pixelRep = 0; } if (colorModel != "MONOCHROME2") { DCMSEG_WARN("Photometric Interpretation is not set correctly (ignored), assuming value MONOCHROME2 as required " "for segmentation objects"); colorModel = "MONOCHROME2"; } if (rows == 0) { DCMSEG_ERROR("Rows is not set correctly (0)"); fail = OFTrue; } if (cols == 0) { DCMSEG_ERROR("Columns is not set correctly (0)"); fail = OFTrue; } if (fail) return EC_InvalidValue; return EC_Normal; } OFCondition DcmSegmentation::writeDataset(DcmItem& dataset) { Uint8* pixData = NULL; size_t pixDataLength; OFCondition result = writeWithSeparatePixelData(dataset, pixData, pixDataLength); if (result.good()) { // Check whether pixel data length exceeds maximum number of bytes for uncompressed pixel data, // enforced by length field of Pixel Data attribute VR OB/OW if written in explicit VR transfer syntax. if (pixDataLength <= 4294967294UL) { result = dataset.putAndInsertUint8Array(DCM_PixelData, pixData, OFstatic_cast(unsigned long, pixDataLength)); } else { result = FG_EC_PixelDataTooLarge; } delete[] pixData; } return result; } OFCondition DcmSegmentation::writeConcatenation(ConcatenationCreator& cc) { Uint8* pixData = NULL; size_t pixDataLength = 0; DcmItem* item = new DcmItem(); if (!item) return EC_MemoryExhausted; OFCondition result = writeWithSeparatePixelData(*item, pixData, pixDataLength); if (result.good()) { result = cc.setCfgInput(item, pixData, pixDataLength, OFTrue /* transfer ownership */); } return result; } OFCondition DcmSegmentation::writeMultiFrameFunctionalGroupsModule(DcmItem& dataset) { Uint32 numFrames = DcmIODUtil::limitMaxFrames( m_Frames.size(), "More than 2147483647 frames provided, limiting Number of Frames to 2147483647"); m_FG.setNumberOfFrames(numFrames); OFCondition result = m_FG.write(dataset); if (result.good()) m_FGInterface.write(dataset); return result; } OFCondition DcmSegmentation::writeMultiFrameDimensionModule(DcmItem& dataset) { return m_DimensionModule.write(dataset); } OFCondition DcmSegmentation::writeFractionalFrames(Uint8* pixData) { OFVector::iterator it = m_Frames.begin(); // Just copy bytes for each frame as is for (size_t count = 0; it != m_Frames.end(); count++) { memcpy(pixData + count * (*it)->length, (*it)->pixData, (*it)->length); it++; } return EC_Normal; } OFCondition DcmSegmentation::writeBinaryFrames(Uint8* pixData, Uint16 rows, Uint16 cols, const size_t pixDataLength) { // Holds the pixels for all frames. Each bit represents a pixel which is either // 1 (part of segment) or 0 (not part of segment. All frames are directly // concatenated, i.e. there are no unused bits between the frames. memset(pixData, 0, pixDataLength); // Fill Pixel Data Element concatFrames(m_Frames, pixData, rows * cols); return EC_Normal; } OFCondition DcmSegmentation::writeSegmentationImageModule(DcmItem& dataset) { dataset.putAndInsertOFStringArray(DCM_ImageType, "DERIVED\\PRIMARY"); OFCondition result = m_ContentIdentificationMacro.write(dataset); /* Write hardcoded values */ if (result.good()) { getImagePixel().setSamplesPerPixel(1); getImagePixel().setPhotometricInterpretation("MONOCHROME2"); getImagePixel().setPixelRepresentation(0); /* Write Bits Allocated/Stored, High Bit, Segmentation Fractional Type, * Segmentation Type, Maximum Fractional Value */ switch (m_SegmentationType) { case DcmSegTypes::ST_BINARY: { getImagePixel().setBitsAllocated(1); getImagePixel().setBitsStored(1); getImagePixel().setHighBit(0); dataset.putAndInsertOFStringArray(DCM_SegmentationType, "BINARY"); break; } case DcmSegTypes::ST_FRACTIONAL: { getImagePixel().setBitsAllocated(8); getImagePixel().setBitsStored(8); getImagePixel().setHighBit(7); dataset.putAndInsertOFStringArray(DCM_SegmentationType, "FRACTIONAL"); if (m_SegmentationFractionalType == DcmSegTypes::SFT_OCCUPANCY) { dataset.putAndInsertOFStringArray(DCM_SegmentationFractionalType, "OCCUPANCY"); } else { dataset.putAndInsertOFStringArray(DCM_SegmentationFractionalType, "PROBABILITY"); } // Maximum Fractional Value: Attribute is type 1C but "required if .. FRACTIONAL", i.e. write type 1 DcmIODUtil::copyElementToDataset( result, dataset, m_MaximumFractionalValue, "1", "1", "SegmentationImageModule"); break; } case DcmSegTypes::ST_UNKNOWN: { DCMSEG_ERROR("Internal error, segmentation type not set"); result = EC_InternalError; break; } } } /* Write segments */ OFVector segmentItems; if (result.good()) { OFVector::iterator it = m_Segments.begin(); dataset.findAndDeleteElement(DCM_SegmentSequence); for (Uint16 itemCount = 0; (it != m_Segments.end()) && result.good(); itemCount++) { DcmItem* segmentItem = NULL; dataset.findOrCreateSequenceItem(DCM_SegmentSequence, segmentItem, itemCount); if (segmentItem) { result = (*it)->write(*segmentItem); /* Insert segment number for the segment, starting from 1 and increasing monotonically. */ if (result.good()) { segmentItem->putAndInsertUint16(DCM_SegmentNumber, itemCount + 1); } } else { DCMIOD_ERROR("Cannot create/get item in Segment Sequence (internal error)"); result = EC_InternalError; } it++; } } return result; } // -- private helpers -- void DcmSegmentation::clearData() { DcmSegmentation::IODImage::clearData(); m_FG.clearData(); m_FGInterface.clear(); DcmIODUtil::freeContainer(m_Frames); DcmIODUtil::freeContainer(m_Segments); m_MaximumFractionalValue.clear(); m_SegmentationFractionalType = DcmSegTypes::SFT_UNKNOWN; m_SegmentationType = DcmSegTypes::ST_UNKNOWN; } OFBool DcmSegmentation::checkPixDataLength(DcmElement* pixelData, const Uint16 rows, const Uint16 cols, const Uint32& numberOfFrames) { // Get actual length of pixel data in bytes size_t length = pixelData->getLengthField(); // Find out how many bytes are needed size_t bytesRequired = 0; OFCondition result = getTotalBytesRequired(rows, cols, numberOfFrames, bytesRequired); if (result.bad()) return OFFalse; // Length found in Pixel Data element is always even if (bytesRequired % 2 == 1) bytesRequired++; /* Compare expected and actual length */ if (length < bytesRequired) { DCMSEG_ERROR("Not enough bytes found in Pixel Data element. Found " << length << " bytes but " << bytesRequired << " bytes expected"); return OFFalse; } else if (length > bytesRequired) { DCMSEG_WARN("Too many bytes found in Pixel Data element. Found " << length << " bytes but " << bytesRequired << " bytes expected"); return OFTrue; } else { DCMSEG_TRACE("Found " << length << " bytes in Pixel Data element as expected"); } return OFTrue; } OFCondition DcmSegmentation::getTotalBytesRequired(const Uint16& rows, const Uint16& cols, const Uint32& numberOfFrames, size_t& bytesRequired) { OFBool ok = OFStandard::safeMult(OFstatic_cast(size_t, rows), OFstatic_cast(size_t, cols), bytesRequired); if (ok) OFStandard::safeMult(bytesRequired, OFstatic_cast(size_t, numberOfFrames), bytesRequired); if (!ok) { DCMSEG_ERROR("Cannot compute number of bytes required for Pixel Data since size_t type is too small"); return EC_TooManyBytesRequested; } /* for binary, we only need one bit per pixel */ size_t remainder = 0; if (m_SegmentationType == DcmSegTypes::ST_BINARY) { // check whether the 1-bit pixels exactly fit into bytes remainder = bytesRequired % 8; // number of bytes that work on an exact fit bytesRequired = bytesRequired / 8; // add one byte if we have a remainder if (remainder > 0) { bytesRequired++; } } return EC_Normal; } OFCondition DcmSegmentation::loadFile(DcmFileFormat& dcmff, const OFString& filename, DcmDataset*& dset) { dset = NULL; OFCondition result = dcmff.loadFile(filename.c_str()); if (result.bad()) { DCMSEG_ERROR("Could not load file " << filename << ": " << result.text()); return result; } dset = dcmff.getDataset(); if (dset == NULL) { DCMSEG_ERROR("Could not load file " << filename << ": No dataset"); return IOD_EC_InvalidObject; } return result; } OFCondition DcmSegmentation::readSegmentationFractionalType(DcmItem& item) { m_SegmentationFractionalType = DcmSegTypes::SFT_UNKNOWN; if (!item.tagExists(DCM_SegmentationFractionalType)) { return EC_TagNotFound; } DcmCodeString element(DCM_SegmentationFractionalType); OFCondition result = DcmIODUtil::getAndCheckElementFromDataset( item, element, getRules()->getByTag(DCM_SegmentationFractionalType)); OFString str; if (result.good()) { element.getOFStringArray(str); m_SegmentationFractionalType = DcmSegTypes::OFString2FractionalType(str); } if (m_SegmentationFractionalType == DcmSegTypes::SFT_UNKNOWN) { DCMSEG_ERROR("Invalid value for attribute Segmentation Fractional Type: " << str); return EC_InvalidValue; } else return EC_Normal; } OFCondition DcmSegmentation::readSegmentationType(DcmItem& item) { m_SegmentationType = DcmSegTypes::ST_UNKNOWN; if (!item.tagExists(DCM_SegmentationType)) { return EC_TagNotFound; } DcmCodeString element(DCM_SegmentationType); OFCondition result = DcmIODUtil::getAndCheckElementFromDataset(item, element, getRules()->getByTag(DCM_SegmentationType)); OFString str; if (result.good()) { element.getOFStringArray(str); m_SegmentationType = DcmSegTypes::OFString2Segtype(str); } if (m_SegmentationType == DcmSegTypes::ST_UNKNOWN) { DCMSEG_ERROR("Invalid value for attribute Segmentation Type: " << str); result = EC_InvalidValue; } return result; } // protected override of public base class function IODImagePixelModule& DcmSegmentation::getImagePixel() { return *OFget >(&DcmSegmentation::IODImage::getImagePixel()); } OFBool DcmSegmentation::check(const OFBool checkFGStructure) { if (m_Frames.size() == 0) { DCMSEG_ERROR("No frame data available"); return OFFalse; } if (m_Segments.size() == 0) { DCMSEG_ERROR("No segments defined"); return OFFalse; } if (m_Segments.size() > DCM_SEG_MAX_SEGMENTS) { DCMSEG_ERROR("Too many segments defined"); return OFFalse; } if (m_Segments.size() > m_Frames.size()) { DCMSEG_ERROR("More segments than frames defined"); return OFFalse; } if (checkFGStructure) { if (!m_FGInterface.check()) return OFFalse; } // Check rules around Frame of Reference // 1. If Derivation Image FG is not present, Frame of Reference is required. OFBool frameOfRefRequired = OFFalse; FGBase* group = m_FGInterface.get(OFstatic_cast(Uint32, 0), DcmFGTypes::EFG_DERIVATIONIMAGE); if (group == NULL) { // Derivation Image FG is not present, FoR is required frameOfRefRequired = OFTrue; } else { // Derivation Image FG present, Frame of Reference is not required frameOfRefRequired = OFFalse; } OFString frameOfRef; getFrameOfReference().getFrameOfReferenceUID(frameOfRef); if (frameOfRefRequired && frameOfRef.empty()) { DCMSEG_ERROR("Frame of Reference UID is not set for Segmentation but is required"); return OFFalse; } // 2. When a Frame of Reference UID is present the segment shall be specified // within that coordinate system, using the Pixel Measures, Plane Position // (Patient) and Plane Orientation (Patient) Functional Groups. if (!frameOfRef.empty()) { // Check that each of above FGs is present. We do not check this for // all frames since if it exists for one frame it must exist for all others. // This is a general rule and applies for all FGs, so it is not checked here. group = m_FGInterface.get(OFstatic_cast(Uint32, 0), DcmFGTypes::EFG_PIXELMEASURES); if (!group) { DCMSEG_ERROR("Frame of Reference UID is present but Pixel Measures FG is missing"); return OFFalse; } group = m_FGInterface.get(OFstatic_cast(Uint32, 0), DcmFGTypes::EFG_PLANEPOSPATIENT); if (!group) { DCMSEG_ERROR("Frame of Reference UID is present but Plane Position (Patient) FG is missing"); return OFFalse; } group = m_FGInterface.get(OFstatic_cast(Uint32, 0), DcmFGTypes::EFG_PLANEORIENTPATIENT); if (!group) { DCMSEG_ERROR("Frame of Reference UID is present but Plane Orientation (Patient) FG is missing"); return OFFalse; } } // Another condition cannot be checked since we do not have access to the // datasets of the source images: // 3. If FoR is present but not the same in images this segmentation applies to, // (those in Derivation Image FG), each pixel of the segmentation shall // correspond to a pixel in a referenced image (i.e. they must share the same // size and resolution). return OFTrue; } OFCondition DcmSegmentation::decompress(DcmDataset& dset) { DcmXfer xfer = dset.getOriginalXfer(); OFCondition result; // If the original transfer syntax could have been lossy, print warning if (dset.hasRepresentation(EXS_LittleEndianExplicit, NULL)) { if (xfer.isEncapsulated() && (xfer.getXfer() != EXS_RLELossless) && (xfer.getXfer() != EXS_DeflatedLittleEndianExplicit)) { DCMSEG_WARN("Dataset has been compressed using a (possibly) lossy compression scheme (ignored)"); } } // If the original transfer is encapsulated and we do not already have an uncompressed version, decompress or reject // the file else if (xfer.isEncapsulated()) { // RLE compression is fine (truly lossless). Deflated is handled internally by DCMTK. if (xfer.getXfer() == EXS_RLELossless) { DCMSEG_DEBUG("DICOM file is RLE-compressed, converting to uncompressed transfer syntax first"); result = DcmIODUtil::decompress(dset); } else // We do not accept any transfer syntax that could be lossy compressed { DCMSEG_ERROR("Transfer syntax " << DcmXfer(xfer).getXferName() << " uses lossy compression, not supported for Segmentation objects!"); result = IOD_EC_CannotDecompress; } } return result; } void DcmSegmentation::concatFrames(OFVector frames, Uint8* pixData, const size_t bitsPerFrame) { // Writing position within the pixData memory Uint8* writePos = pixData; OFVector::iterator frame = frames.begin(); Uint8 freeBits = 0; Uint8 firstByte = 0; // Iterate over frames and copy each to pixData memory for (size_t f = 0; frame != frames.end(); f++) { DCMSEG_DEBUG("Packing segmentation frame #" << f + 1 << "/" << frames.size()); // Backup first byte of the destination since it may contain bits of the // previous frame; mask out those bits not belonging to previous frame. // This will potentially create some empty bits on the left of the byte, // that the current frame can use to store the its own first bits. firstByte = OFstatic_cast(unsigned char, (writePos[0] << freeBits)) >> freeBits; memcpy(writePos, (*frame)->pixData, (*frame)->length); // If the previous frame left over some unused bits, shift the current frame // that number of bits to the left, and restore the original bits of the // previous frame that are overwritten by the shifting operation. if (freeBits > 0) { DcmSegUtils::alignFrameOnBitPosition(writePos, (*frame)->length, 8 - freeBits); writePos[0] |= firstByte; } // Compute free bits left over from this frame in the previous byte written freeBits = (8 - (((f + 1) * bitsPerFrame) % 8)) % 8; // If we have free bits, the previous byte written to will be the first byte // we write to for the next frame. Otherwise start with a fresh destination // byte. if (freeBits > 0) { writePos = writePos + (*frame)->length - 1; } else { writePos = writePos + (*frame)->length; } // Next frame frame++; } // Through shifting we can have non-zero bits within the unused bits of the // last byte. Fill them with zeros (though not required by the standard). if (freeBits > 0) { *writePos = (OFstatic_cast(unsigned char, *writePos) >> freeBits) << freeBits; } }