/* * * Copyright (C) 2007-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: dcmdata * * Author: Michael Onken * * Purpose: Implements utility for converting standard image formats to DICOM * */ #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ #include "dcmtk/dcmdata/libi2d/i2d.h" #include "dcmtk/dcmdata/dcpxitem.h" #include "dcmtk/dcmdata/dcfilefo.h" /* for DcmFileFormat */ #include "dcmtk/dcmdata/dcdeftag.h" /* for DCM_ defines */ #include "dcmtk/dcmdata/dcuid.h" /* for SITE_SERIES_UID_ROOT */ #include "dcmtk/dcmdata/dcpixseq.h" /* for DcmPixelSequence */ #include "dcmtk/dcmdata/dcpath.h" /* for override keys */ #include "dcmtk/dcmdata/xml2dcm.h" /* for DcmXMLParseHelper */ OFLogger DCM_dcmdataLibi2dLogger = OFLog::getLogger("dcmtk.dcmdata.libi2d"); Image2Dcm::Image2Dcm() : m_overrideKeys() , m_templateFile("") , m_templateFileIsXML(OFFalse) , m_XMLvalidation(OFFalse) , m_XMLnamespaceCheck(OFFalse) , m_readStudyLevel(OFFalse) , m_readSeriesLevel(OFFalse) , m_studySeriesFile() , m_incInstNoFromFile(OFFalse) , m_disableAttribChecks(OFFalse) , m_inventMissingType2Attribs(OFTrue) , m_inventMissingType1Attribs(OFFalse) , m_rows(0) , m_cols(0) , m_samplesPerPixel(0) , m_bitsAllocated(0) , m_bitsStored(0) , m_highBit(0) , m_pixelRepresentation(0) , m_planarConfiguration(0) , m_pixelAspectRatioH(0) , m_pixelAspectRatioV(0) , m_frameLength(0) , m_photometricInterpretation() , m_compressionRatio(1.0) , m_conversionFlags(0) , m_output_buffer(NULL) , m_offsetList() , m_pixelSequence(NULL) , m_offsetTable(NULL) { } Image2Dcm::~Image2Dcm() { } OFCondition Image2Dcm::convertFirstFrame( I2DImgSource *inputPlug, I2DOutputPlug *outPlug, size_t numberOfFrames, DcmDataset*& resultDset, E_TransferSyntax& proposedTS) { if (!inputPlug || !outPlug || (numberOfFrames < 1)) return EC_IllegalParameter; OFCondition cond; resultDset = NULL; OFunique_ptr tempDataset; DCMDATA_LIBI2D_DEBUG("Image2Dcm: Starting conversion of file: " << inputPlug->getImageFile()); // If specified, copy DICOM template file to export file if (!m_templateFile.empty()) { DcmFileFormat dcmff; #ifdef WITH_LIBXML if (m_templateFileIsXML) { DcmXMLParseHelper parser; E_TransferSyntax xfer; cond = parser.readXmlFile(m_templateFile.c_str(), dcmff, xfer, OFFalse /* ignore metaheader */, m_XMLnamespaceCheck, m_XMLvalidation, OFTrue /* stop on errors */); } else { cond = dcmff.loadFile(m_templateFile.c_str()); } #else cond = dcmff.loadFile(m_templateFile.c_str()); #endif if (cond.bad()) return cond; // remove problematic attributes from dataset cleanupTemplate(dcmff.getDataset()); // copy from input file tempDataset.reset(new DcmDataset(*(dcmff.getDataset()))); } else // otherwise, start with an empty DICOM file tempDataset.reset(new DcmDataset()); if (!tempDataset.get()) return EC_MemoryExhausted; // Read patient and study or series information if desired and write to export file if (m_readStudyLevel || m_readSeriesLevel) { cond = applyStudyOrSeriesFromFile(tempDataset.get()); if (cond.bad()) { return cond; } } // Increment instance number if (m_incInstNoFromFile) { cond = incrementInstanceNumber(tempDataset.get()); if (cond.bad()) { return cond; } } // Generate and insert UIDs as necessary generateUIDs(tempDataset.get()); // Read and insert pixel data m_compressionRatio = 1.0; cond = readAndInsertPixelDataFirstFrame(inputPlug, numberOfFrames, tempDataset.get(), proposedTS, m_compressionRatio); if (cond.bad()) { return cond; } // Insert SOP Class specific attributes (and values) cond = outPlug->convert(*tempDataset); if (cond.bad()) { return cond; } // Insert SOP Class specific attributes (and values) cond = outPlug->insertMultiFrameAttributes(tempDataset.get(), numberOfFrames); if (cond.bad()) { return cond; } // At last, apply override keys on dataset cond = applyOverrideKeys(tempDataset.get()); if (cond.bad()) { return cond; } // Do some very basic attribute checking (e. g. existence (type 2) and values (type 1)) if (!m_disableAttribChecks) { OFString err; err = isValid(*tempDataset); err += outPlug->isValid(*tempDataset); if (!err.empty()) { return makeOFCondition(OFM_dcmdata, 18, OF_error, err.c_str()); } } resultDset = tempDataset.release(); return EC_Normal; } OFCondition Image2Dcm::updateLossyCompressionInfo( I2DImgSource *inputPlug, size_t numberOfFrames, DcmDataset *dset) { // Insert Lossy Image Compression and Lossy Image Compression Method attributes if necessary OFBool srcIsLossy = OFFalse; OFString comprMethod; OFCondition cond; if (inputPlug->getLossyComprInfo(srcIsLossy, comprMethod).good()) { if (srcIsLossy) { double compressionRatio = m_compressionRatio; if (numberOfFrames > 0) compressionRatio = m_compressionRatio / OFstatic_cast(double, numberOfFrames); cond = dset->putAndInsertOFStringArray(DCM_LossyImageCompression, "01"); if (cond.good() && !comprMethod.empty()) cond = dset->putAndInsertOFStringArray(DCM_LossyImageCompressionMethod, comprMethod); if (cond.good()) { char buf[64]; OFStandard::ftoa(buf, sizeof(buf), compressionRatio, OFStandard::ftoa_uppercase, 0, 5); cond = dset->putAndInsertOFStringArray(DCM_LossyImageCompressionRatio, buf); } if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to write attribute Lossy Image Compression and/or Lossy Image Compression Method to result dataset"); } } else DCMDATA_LIBI2D_DEBUG("Image2Dcm: No information regarding lossy compression available"); return EC_Normal; } OFCondition Image2Dcm::convertNextFrame( I2DImgSource *inputPlug, size_t frameNumber) { if (!inputPlug || (frameNumber < 2)) return EC_IllegalParameter; DCMDATA_LIBI2D_DEBUG("Image2Dcm: Starting conversion of file: " << inputPlug->getImageFile()); // Read and insert pixel data return readAndInsertPixelDataNextFrame(inputPlug, frameNumber); } void Image2Dcm::setConversionFlags(size_t conversionFlags) { m_conversionFlags = conversionFlags; } void Image2Dcm::cleanupTemplate(DcmDataset *targetDset) { if (!targetDset) return; // Remove any existing image pixel module attribute targetDset->findAndDeleteElement(DCM_PixelDataProviderURL); targetDset->findAndDeleteElement(DCM_PhotometricInterpretation); targetDset->findAndDeleteElement(DCM_SamplesPerPixel); targetDset->findAndDeleteElement(DCM_Rows); targetDset->findAndDeleteElement(DCM_Columns); targetDset->findAndDeleteElement(DCM_BitsAllocated); targetDset->findAndDeleteElement(DCM_BitsStored); targetDset->findAndDeleteElement(DCM_HighBit); targetDset->findAndDeleteElement(DCM_PixelRepresentation); targetDset->findAndDeleteElement(DCM_PixelData); targetDset->findAndDeleteElement(DCM_PlanarConfiguration); targetDset->findAndDeleteElement(DCM_PixelAspectRatio); targetDset->findAndDeleteElement(DCM_SmallestImagePixelValue); targetDset->findAndDeleteElement(DCM_LargestImagePixelValue); targetDset->findAndDeleteElement(DCM_RedPaletteColorLookupTableDescriptor); targetDset->findAndDeleteElement(DCM_GreenPaletteColorLookupTableDescriptor); targetDset->findAndDeleteElement(DCM_BluePaletteColorLookupTableDescriptor); targetDset->findAndDeleteElement(DCM_RedPaletteColorLookupTableData); targetDset->findAndDeleteElement(DCM_GreenPaletteColorLookupTableData); targetDset->findAndDeleteElement(DCM_BluePaletteColorLookupTableData); targetDset->findAndDeleteElement(DCM_ICCProfile); // Remove SOP Class / Instance information targetDset->findAndDeleteElement(DCM_SOPClassUID); targetDset->findAndDeleteElement(DCM_SOPInstanceUID); } OFCondition Image2Dcm::applyStudyOrSeriesFromFile(DcmDataset *targetDset) { DCMDATA_LIBI2D_DEBUG("Image2Dcm: Applying study and/or series information from file"); if ( (!m_readSeriesLevel && !m_readStudyLevel) || m_studySeriesFile.empty() ) return EC_IllegalCall; DcmFileFormat dcmff; OFString errMsg; OFCondition cond; // Open DICOM file to read patient/study/series information from cond = dcmff.loadFile(m_studySeriesFile.c_str()); if (cond.bad()) { errMsg = "Error: Unable to open study / series file "; errMsg += m_studySeriesFile; return makeOFCondition(OFM_dcmdata, 18, OF_error, errMsg.c_str()); } DcmDataset *srcDset = NULL; srcDset = dcmff.getDataset(); if (srcDset == NULL) return EC_IllegalCall; // Determine the specific character set of the target dataset OFString value; OFString targetCharSet; (void) targetDset->findAndGetOFString(DCM_SpecificCharacterSet, targetCharSet); // Determine the specific character set of the target dataset of the Study/Series file (void) srcDset->findAndGetOFString(DCM_SpecificCharacterSet, value); if (targetCharSet == value) { // case 1: character sets are identical (or both empty), nothing to do. } else if (value.empty()) { // case 2: target dataset uses an extended character set, // while the study/series file uses ASCII. Nothing to do. } else if (targetCharSet.empty()) { // case 3: the target dataset uses ASCII, while the // study/series file uses an extended character set. // Use the character set from the study/series file. cond = targetDset->putAndInsertOFStringArray(DCM_SpecificCharacterSet, value); if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to write Specific Character Set to file"); } else { // case 4: target dataset and study/series files use different // extended character sets. We will try to convert the study/series file // to the character set used in the target dataset and fail if this // does not succeed. DCMDATA_LIBI2D_DEBUG("Image2Dcm: target charset: " << targetCharSet << ", study/series file charset: " << value); cond = dcmff.convertCharacterSet(targetCharSet, m_conversionFlags); if (cond.bad()) { return makeOFCondition(OFM_dcmdata, 18, OF_error, "Character set mismatch between template and study/series file"); } } // Patient level attributes (type 2 - if value cannot be read, insert empty value value.clear(); srcDset->findAndGetOFString(DCM_PatientName, value); cond = targetDset->putAndInsertOFStringArray(DCM_PatientName, value); if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to write Patient's Name to file"); value.clear(); srcDset->findAndGetOFString(DCM_PatientID, value); cond = targetDset->putAndInsertOFStringArray(DCM_PatientID, value); if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to write Patient ID to file"); value.clear(); srcDset->findAndGetOFString(DCM_PatientSex, value); cond = targetDset->putAndInsertOFStringArray(DCM_PatientSex, value); if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to write Patient's Sex to file"); value.clear(); srcDset->findAndGetOFString(DCM_PatientBirthDate, value); cond = targetDset->putAndInsertOFStringArray(DCM_PatientBirthDate, value); if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to write Patient's Birth Date to file"); value.clear(); // Study level attributes (type 2 except Study Instance UID) cond = srcDset->findAndGetOFString(DCM_StudyInstanceUID, value); if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to read Study Instance UID (type 1) from file"); cond = targetDset->putAndInsertOFStringArray(DCM_StudyInstanceUID, value); if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to write Study Instance UID to file"); value.clear(); srcDset->findAndGetOFString(DCM_StudyDate, value); cond = targetDset->putAndInsertOFStringArray(DCM_StudyDate, value); if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to write Study Date to file"); value.clear(); srcDset->findAndGetOFString(DCM_StudyTime, value); cond = targetDset->putAndInsertOFStringArray(DCM_StudyTime, value); if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to write Study Time to file"); value.clear(); srcDset->findAndGetOFString(DCM_ReferringPhysicianName, value); cond = targetDset->putAndInsertOFStringArray(DCM_ReferringPhysicianName, value); if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to write Referring Physician's Name to file"); value.clear(); srcDset->findAndGetOFString(DCM_StudyID, value); cond = targetDset->putAndInsertOFStringArray(DCM_StudyID, value); if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to write Study ID to file"); value.clear(); srcDset->findAndGetOFString(DCM_AccessionNumber, value); cond = targetDset->putAndInsertOFStringArray(DCM_AccessionNumber, value); if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to trite Accession Number to file"); value.clear(); // Series Level attributes (type 2 except Series Instance UID which is type 1) if (m_readSeriesLevel) { // General Series Module attributes cond = srcDset->findAndGetOFString(DCM_SeriesInstanceUID, value); if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to read Series Instance UID (type 1) from file"); cond = targetDset->putAndInsertOFStringArray(DCM_SeriesInstanceUID, value); if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to write Series Instance UID to file"); value.clear(); srcDset->findAndGetOFString(DCM_SeriesNumber, value); cond = targetDset->putAndInsertOFStringArray(DCM_SeriesNumber, value); if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to write Series Number to file"); value.clear(); // General Equipment Module attributes srcDset->findAndGetOFString(DCM_Manufacturer, value); cond = targetDset->putAndInsertOFStringArray(DCM_Manufacturer, value); if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to write Manufacturer to file"); value.clear(); } // We need to copy Instance Number if we are supposed to increase it if (m_incInstNoFromFile) { cond = srcDset->findAndGetOFString(DCM_InstanceNumber, value); if (cond.bad()) value = "1"; cond = targetDset->putAndInsertOFStringArray(DCM_InstanceNumber, value); if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to write Instance Number to file"); } return EC_Normal; } OFCondition Image2Dcm::incrementInstanceNumber(DcmDataset *targetDset) { // Read and increment Instance Number if desired if (m_incInstNoFromFile) { DCMDATA_LIBI2D_DEBUG("Image2Dcm: Trying to read and increment instance number"); Sint32 instanceNumber; if ( targetDset->findAndGetSint32(DCM_InstanceNumber, instanceNumber).good() ) { instanceNumber++; char buf[100]; sprintf(buf, "%ld", OFstatic_cast(long, instanceNumber)); OFCondition cond = targetDset->putAndInsertOFStringArray(DCM_InstanceNumber, buf); if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable write Instance Number to dataset"); } else return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to read Instance Number from dataset"); } return EC_Normal; } OFCondition Image2Dcm::generateUIDs(DcmDataset *dset) { OFString value; OFCondition cond; DCMDATA_LIBI2D_DEBUG("Image2Dcm: Generate and insert new UIDs if necessary"); // Generate and write Series Instance UID if not already present if (!m_readSeriesLevel) { cond = dset->findAndGetOFString(DCM_SeriesInstanceUID, value); if (cond.bad() || value.empty()) { char newUID[100]; dcmGenerateUniqueIdentifier(newUID, SITE_SERIES_UID_ROOT); cond = dset->putAndInsertOFStringArray(DCM_SeriesInstanceUID, newUID); if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to write Series Instance UID to file"); } value.clear(); } // Generate and write Study Instance UID if not already present if (!m_readStudyLevel) { cond = dset->findAndGetOFString(DCM_StudyInstanceUID, value); if (cond.bad() || value.empty()) { char newUID[100]; dcmGenerateUniqueIdentifier(newUID, SITE_STUDY_UID_ROOT); cond = dset->putAndInsertOFStringArray(DCM_StudyInstanceUID, newUID); if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to write Study Instance UID to file"); } value.clear(); } // Generate SOP Instance UID if not already present cond = dset->findAndGetOFString(DCM_SOPInstanceUID, value); if (cond.bad() || value.empty()) { char newUID[100]; dcmGenerateUniqueIdentifier(newUID, SITE_INSTANCE_UID_ROOT); cond = dset->putAndInsertOFStringArray(DCM_SOPInstanceUID, newUID); if (cond.bad()) return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to write SOP Instance UID to file"); } return EC_Normal; } OFCondition Image2Dcm::insertEncapsulatedPixelDataFirstFrame( DcmDataset* dset, char *pixData, Uint32 length, E_TransferSyntax outputTS) { OFCondition cond; DCMDATA_LIBI2D_DEBUG("Image2Dcm: Storing imported pixel data to DICOM file"); // create initial pixel sequence delete m_pixelSequence; m_pixelSequence = new DcmPixelSequence(DCM_PixelSequenceTag); // insert empty offset table into sequence delete m_offsetTable; m_offsetTable = new DcmPixelItem(DCM_PixelItemTag); cond = m_pixelSequence->insert(m_offsetTable); if (cond.bad()) { delete m_offsetTable; m_offsetTable = NULL; delete m_pixelSequence; m_pixelSequence = NULL; return cond; } // store compressed frame into pixel sequence cond = m_pixelSequence->storeCompressedFrame(m_offsetList, OFreinterpret_cast(Uint8*,pixData), length, 0); if (cond.bad()) { delete m_pixelSequence; m_pixelSequence = NULL; return cond; } // insert pixel data attribute incorporating pixel sequence into dataset DcmPixelData *pixelData = new DcmPixelData(DCM_PixelData); /* tell pixel data element that this is the original presentation of the pixel data * pixel data and how it compressed */ pixelData->putOriginalRepresentation(outputTS, NULL, m_pixelSequence); cond = dset->insert(pixelData); if (cond.bad()) { delete m_pixelSequence; m_pixelSequence = NULL; // also deletes contained pixel sequence return cond; } return EC_Normal; } OFCondition Image2Dcm::insertEncapsulatedPixelDataNextFrame( char *pixData, Uint32 length) { if (m_pixelSequence == NULL || m_offsetTable == NULL) return EC_IllegalCall; DCMDATA_LIBI2D_DEBUG("Image2Dcm: Storing imported pixel data to DICOM file"); // store compressed frame into pixel sequence return m_pixelSequence->storeCompressedFrame(m_offsetList, OFreinterpret_cast(Uint8*,pixData), length, 0); } OFCondition Image2Dcm::readAndInsertPixelDataFirstFrame( I2DImgSource* imgSource, size_t numberOfFrames, DcmDataset* dset, E_TransferSyntax& outputTS, double& compressionRatio) { m_pixelAspectRatioH =1; m_pixelAspectRatioV = 1; outputTS = EXS_Unknown; char* pixData = NULL; OFCondition cond = imgSource->readPixelData(m_rows, m_cols, m_samplesPerPixel, m_photometricInterpretation, m_bitsAllocated, m_bitsStored, m_highBit, m_pixelRepresentation, m_planarConfiguration, m_pixelAspectRatioH, m_pixelAspectRatioV, pixData, m_frameLength, outputTS); if (cond.bad()) return cond; // compute compression ratio for this frame compressionRatio = 1.0; double uncompressedSize = m_cols * m_rows; uncompressedSize = uncompressedSize * m_bitsStored * m_samplesPerPixel / 8.0; if (m_frameLength > 0) compressionRatio = uncompressedSize / m_frameLength; DcmXfer transport(outputTS); if (transport.isEncapsulated()) { m_offsetList.clear(); insertEncapsulatedPixelDataFirstFrame(dset, pixData, m_frameLength, outputTS); delete[] pixData; } else { /* Not encapsulated */ DcmPixelData *pixelData = new DcmPixelData(DCM_PixelData); if (pixelData == NULL) { delete[] pixData; return EC_MemoryExhausted; } cond = dset->insert(pixelData); if (cond.bad()) { delete[] pixData; delete pixelData; return cond; } DCMDATA_LIBI2D_DEBUG("Image2Dcm: frame size=" << m_frameLength << ", number of frames=" << numberOfFrames << ", length of pixel data array=" << ((m_frameLength * numberOfFrames + 1)/2)*2); Uint16 *array = NULL; size_t arraySize = (m_frameLength * numberOfFrames + 1)/2; cond = pixelData->createUint16Array(OFstatic_cast(Uint32, arraySize), array); if (cond.bad()) { delete[] pixData; return cond; } m_output_buffer = OFreinterpret_cast(char *, array); memcpy(array, pixData, m_frameLength); delete[] pixData; } DCMDATA_LIBI2D_DEBUG("Image2Dcm: Inserting Image Pixel module information"); cond = dset->putAndInsertUint16(DCM_SamplesPerPixel, m_samplesPerPixel); if (cond.bad()) return cond; cond = dset->putAndInsertOFStringArray(DCM_PhotometricInterpretation, m_photometricInterpretation); if (cond.bad()) return cond; // Should only be written if Samples per Pixel > 1 if (m_samplesPerPixel > 1) { cond = dset->putAndInsertUint16(DCM_PlanarConfiguration, m_planarConfiguration); if (cond.bad()) return cond; } cond = dset->putAndInsertUint16(DCM_Rows, m_rows); if (cond.bad()) return cond; cond = dset->putAndInsertUint16(DCM_Columns, m_cols); if (cond.bad()) return cond; cond = dset->putAndInsertUint16(DCM_BitsAllocated, m_bitsAllocated); if (cond.bad()) return cond; cond = dset->putAndInsertUint16(DCM_BitsStored, m_bitsStored); if (cond.bad()) return cond; cond = dset->putAndInsertUint16(DCM_HighBit, m_highBit); if (cond.bad()) return cond; if ( m_pixelAspectRatioH != m_pixelAspectRatioV ) { char buf[200]; int err = sprintf(buf, "%u\\%u", m_pixelAspectRatioV, m_pixelAspectRatioH); if (err == -1) return EC_IllegalCall; cond = dset->putAndInsertOFStringArray(DCM_PixelAspectRatio, buf); if (cond.bad()) return cond; } return dset->putAndInsertUint16(DCM_PixelRepresentation, m_pixelRepresentation); } OFCondition Image2Dcm::readAndInsertPixelDataNextFrame( I2DImgSource* imageSource, size_t frameNumber) { Uint16 next_pixelAspectRatioH = 1; Uint16 next_pixelAspectRatioV = 1; Uint16 next_rows = 0; Uint16 next_cols = 0; Uint16 next_samplesPerPixel = 0; Uint16 next_bitsAllocated = 0; Uint16 next_bitsStored = 0; Uint16 next_highBit = 0; Uint16 next_pixelRepresentation = 0; Uint16 next_planarConfiguration = 0; Uint32 next_frameLength = 0; OFString next_photometricInterpretation; char *pixData = NULL; E_TransferSyntax outputTS; OFCondition cond = imageSource->readPixelData(next_rows, next_cols, next_samplesPerPixel, next_photometricInterpretation, next_bitsAllocated, next_bitsStored, next_highBit, next_pixelRepresentation, next_planarConfiguration, next_pixelAspectRatioH, next_pixelAspectRatioV, pixData, next_frameLength, outputTS); if (cond.bad()) return cond; // create a transfer syntax object for the output transfer syntax DcmXfer transport(outputTS); // check consistency between current frame and first frame if (next_rows != m_rows) { cond = makeOFCondition(OFM_dcmdata, 18, OF_error, "Image2Dcm: value of Rows not equal for all frames of the multi-frame image"); return cond; } if (next_cols != m_cols) { cond = makeOFCondition(OFM_dcmdata, 18, OF_error, "Image2Dcm: value of Columns not equal for all frames of the multi-frame image"); return cond; } if (next_samplesPerPixel != m_samplesPerPixel) { cond = makeOFCondition(OFM_dcmdata, 18, OF_error, "Image2Dcm: value of SamplesPerPixel not equal for all frames of the multi-frame image"); return cond; } if (next_photometricInterpretation != m_photometricInterpretation) { cond = makeOFCondition(OFM_dcmdata, 18, OF_error, "Image2Dcm: value of PhotometricInterpretation not equal for all frames of the multi-frame image"); return cond; } if (next_bitsAllocated != m_bitsAllocated) { cond = makeOFCondition(OFM_dcmdata, 18, OF_error, "Image2Dcm: value of BitsAllocated not equal for all frames of the multi-frame image"); return cond; } if (next_bitsStored != m_bitsStored) { cond = makeOFCondition(OFM_dcmdata, 18, OF_error, "Image2Dcm: value of BitsStored not equal for all frames of the multi-frame image"); return cond; } if (next_highBit != m_highBit) { cond = makeOFCondition(OFM_dcmdata, 18, OF_error, "Image2Dcm: value of HighBit not equal for all frames of the multi-frame image"); return cond; } if (next_pixelRepresentation != m_pixelRepresentation) { cond = makeOFCondition(OFM_dcmdata, 18, OF_error, "Image2Dcm: value of PixelRepresentation not equal for all frames of the multi-frame image"); return cond; } if (next_planarConfiguration != m_planarConfiguration) { cond = makeOFCondition(OFM_dcmdata, 18, OF_error, "Image2Dcm: value of PlanarConfiguration not equal for all frames of the multi-frame image"); return cond; } if (next_pixelAspectRatioH != m_pixelAspectRatioH) { cond = makeOFCondition(OFM_dcmdata, 18, OF_error, "Image2Dcm: value of horizontal PixelAspectRatio not equal for all frames of the multi-frame image"); return cond; } if (next_pixelAspectRatioV != m_pixelAspectRatioV) { cond = makeOFCondition(OFM_dcmdata, 18, OF_error, "Image2Dcm: value of vertical PixelAspectRatio not equal for all frames of the multi-frame image"); return cond; } if ((!transport.isEncapsulated()) && (next_frameLength != m_frameLength)) { // in the case of uncompressed images, all frames must have exactly the same size. // for compressed images, where we store the compressed bitstream as a pixel item, this does not matter. cond = makeOFCondition(OFM_dcmdata, 18, OF_error, "Image2Dcm: frame size not equal for all frames of the multi-frame image"); return cond; } // compute compression ratio for this frame double compressionRatio = 1.0; double uncompressedSize = next_cols * next_rows; uncompressedSize = uncompressedSize * next_bitsStored * next_samplesPerPixel / 8.0; if (next_frameLength > 0) compressionRatio = uncompressedSize / next_frameLength; // accumulate compression ratio in m_compressionRatio. // We will divide this by the number of frames later. m_compressionRatio += compressionRatio; if (transport.isEncapsulated()) { cond = insertEncapsulatedPixelDataNextFrame(pixData, next_frameLength); delete[] pixData; return cond; } else { /* Not encapsulated */ if (m_output_buffer == NULL) { cond = makeOFCondition(OFM_dcmdata, 18, OF_error, "Image2Dcm: output buffer not allocated"); delete[] pixData; return cond; } char *array = m_output_buffer + m_frameLength * (frameNumber - 1); memcpy(array, pixData, m_frameLength); delete[] pixData; } return EC_Normal; } OFString Image2Dcm::isValid(DcmDataset& dset) const { DCMDATA_LIBI2D_DEBUG("Image2Dcm: Checking validity of DICOM output dataset"); OFString dummy, err; OFCondition cond; // General Patient module attributes err += checkAndInventType2Attrib(DCM_PatientName, &dset); err += checkAndInventType2Attrib(DCM_PatientSex, &dset); err += checkAndInventType2Attrib(DCM_PatientBirthDate, &dset); err += checkAndInventType2Attrib(DCM_PatientID, &dset); // General Study module attributes err += checkAndInventType1Attrib(DCM_StudyInstanceUID, &dset); err += checkAndInventType2Attrib(DCM_StudyDate, &dset); err += checkAndInventType2Attrib(DCM_StudyTime, &dset); err += checkAndInventType2Attrib(DCM_ReferringPhysicianName, &dset); err += checkAndInventType2Attrib(DCM_StudyID, &dset); err += checkAndInventType2Attrib(DCM_AccessionNumber, &dset); // General Series module attributes err += checkAndInventType1Attrib(DCM_SeriesInstanceUID, &dset); err += checkAndInventType2Attrib(DCM_SeriesNumber, &dset); err += checkAndInventType2Attrib(DCM_InstanceNumber, &dset); // General Image module attributes /* Patient Orientation is of type 2C and must be written if not Image Orientation (Patient) (0020,0037) and Image Position (Patient) are required for the IOD. The current output IODs (SC, new SC, VLP) therefore need Patient Orientation. Make sure any new output plugin takes care about this attribute */ err += checkAndInventType2Attrib(DCM_PatientOrientation, &dset); // Image Pixel Module err += checkAndInventType1Attrib(DCM_Rows, &dset); err += checkAndInventType1Attrib(DCM_Columns, &dset); err += checkAndInventType1Attrib(DCM_SamplesPerPixel, &dset); err += checkAndInventType1Attrib(DCM_PhotometricInterpretation, &dset); err += checkAndInventType1Attrib(DCM_BitsAllocated, &dset); err += checkAndInventType1Attrib(DCM_BitsStored, &dset); err += checkAndInventType1Attrib(DCM_HighBit, &dset); err += checkAndInventType1Attrib(DCM_PixelRepresentation, &dset); err += checkAndInventType1Attrib(DCM_SOPInstanceUID, &dset); return err; } void Image2Dcm::setSeriesFrom(const OFString& file) { m_readSeriesLevel = OFTrue; m_studySeriesFile = file; } void Image2Dcm::setStudyFrom(const OFString& file) { m_readStudyLevel = OFTrue; m_studySeriesFile = file; } void Image2Dcm::setValidityChecking(OFBool doChecks, OFBool insertMissingType2, OFBool inventMissingType1) { m_disableAttribChecks = !doChecks; m_inventMissingType2Attribs = insertMissingType2; m_inventMissingType1Attribs = inventMissingType1; } void Image2Dcm::setTemplateFile(const OFString& file) { m_templateFile = file; } void Image2Dcm::setTemplateFileIsXML(OFBool isXML) { m_templateFileIsXML = isXML; } void Image2Dcm::setXMLvalidation(OFBool enabled) { m_XMLvalidation = enabled; } void Image2Dcm::setXMLnamespaceCheck(OFBool enabled) { m_XMLnamespaceCheck = enabled; } void Image2Dcm::setIncrementInstanceNumber(OFBool incInstNo) { m_incInstNoFromFile = incInstNo; } void Image2Dcm::setOverrideKeys(const OFList& ovkeys) { OFListConstIterator(OFString) it = ovkeys.begin(); OFListConstIterator(OFString) end = ovkeys.end(); while (it != end) { m_overrideKeys.push_back(*it); it++; } } OFCondition Image2Dcm::applyOverrideKeys(DcmDataset *outputDset) { /* replace specific keys by those in overrideKeys, copied from findscu */ OFListConstIterator(OFString) path = m_overrideKeys.begin(); OFListConstIterator(OFString) endOfList = m_overrideKeys.end(); OFCondition cond; DcmPathProcessor proc; while (path != endOfList) { cond = proc.applyPathWithValue(outputDset, *path); if (cond.bad()) { OFString err; err += "Bad override key/path: "; err += *path; err += ": "; err += cond.text(); return makeOFCondition(OFM_dcmdata, 18, OF_error, err.c_str()); } path++; } return cond; } OFString Image2Dcm::checkAndInventType1Attrib(const DcmTagKey& key, DcmDataset* targetDset, const OFString& defaultValue) const { OFBool exists = targetDset->tagExists(key); if (!exists) { OFString err = "Image2Dcm: Missing type 1 attribute: "; err += DcmTag(key).getTagName(); err += "\n"; return err; } DcmElement *elem; OFCondition cond = targetDset->findAndGetElement(key, elem); if (cond.bad() || !elem || (elem->getLength() == 0)) { if (!m_inventMissingType1Attribs) { OFString err = "Image2Dcm: Empty value for type 1 attribute: "; err += DcmTag(key).getTagName(); err += "\n"; return err; } // holds element to insert in item elem = NULL; DcmTag tag(key); OFBool wasError = OFFalse; // if DICOM element could be created, insert in to item and modify to value if ( DcmItem::newDicomElement(elem, tag).good()) { if (targetDset->insert(elem, OFTrue).good()) { if (elem->putString(defaultValue.c_str()).good()) { DCMDATA_LIBI2D_DEBUG("Image2Dcm: Inserting missing type 1 attribute " << tag.getTagName() << " with value " << defaultValue); } else wasError = OFTrue; } else wasError = OFTrue; } else wasError = OFTrue; if (wasError) { OFString err = "Unable to insert type 1 attribute "; err += tag.getTagName(); err += " with value "; err += defaultValue; err += "\n"; return err; } } return ""; } OFString Image2Dcm::checkAndInventType2Attrib(const DcmTagKey& key, DcmDataset* targetDset) const { OFString err; OFBool exists = targetDset->tagExists(key); if (!exists) { if (m_inventMissingType2Attribs) { DcmTag tag(key); DCMDATA_LIBI2D_DEBUG("Image2Dcm: Inserting missing type 2 attribute: " << tag.getTagName()); targetDset->insertEmptyElement(tag); } else { err = "Image2Dcm: Missing type 2 attribute: "; err += DcmTag(key).getTagName(); err += "\n"; return err; } } return err; } OFCondition Image2Dcm::updateOffsetTable() { OFCondition result = EC_Normal; if (m_offsetTable) result = m_offsetTable->createOffsetTable(m_offsetList); return result; }