src/dcmFilePyramidSource.cpp (447 lines of code) (raw):

// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include <dcmtk/dcmdata/dcdeftag.h> #include <dcmtk/dcmdata/dcfilefo.h> #include <dcmtk/dcmdata/dcpixel.h> #include <dcmtk/dcmdata/dcpixseq.h> #include <dcmtk/dcmdata/dcrledrg.h> #include <dcmtk/dcmjpeg/djdecode.h> #include <dcmtk/dcmimage/diregist.h> #include <dcmtk/dcmimgle/diutils.h> #include <dcmtk/dcmimgle/dcmimage.h> #include <string> #include <memory> #include <utility> #include <boost/log/trivial.hpp> #include <boost/thread.hpp> #include <opencv2/opencv.hpp> #include "src/dcmFilePyramidSource.h" #include "src/jpegUtil.h" namespace wsiToDicomConverter { AbstractDicomFileFrame::AbstractDicomFileFrame( int64_t locationX, int64_t locationY, DcmFilePyramidSource* pyramidSource) : BaseFileFrame(locationX, locationY, pyramidSource) { } void AbstractDicomFileFrame::debugLog() const { BOOST_LOG_TRIVIAL(info) << "AbstractDICOM File Frame: "; } // Returns frame component of DCM_DerivationDescription // describes in text how frame imaging data was saved in frame. std::string AbstractDicomFileFrame::derivationDescription() const { return "Generated from DICOM"; } DICOMDatasetReader::DICOMDatasetReader(const std::string &filename) { dcmFile_.loadFile(filename.c_str()); dataset_ = dcmFile_.getDataset(); } DcmDataset *DICOMDatasetReader::dataset() { return dataset_; } boost::mutex* DICOMDatasetReader::datasetMutex() { return &datasetMutex_; } DICOMImageFrame::DICOMImageFrame(int64_t frameNumber, int64_t locationX, int64_t locationY, uint64_t dicomMemSize, DcmFilePyramidSource *pyramidSource): AbstractDicomFileFrame(locationX, locationY, pyramidSource), frameNumber_(frameNumber) { size_ = dicomMemSize; } int64_t DICOMImageFrame::rawABGRFrameBytes(uint8_t *rawMemory, int64_t memorySize) { // DICOM frame data in native format is jpeg encoded. // uncompress and return # of bytes read. // return 0 if error occures. const uint64_t width = frameWidth(); const uint64_t height = frameHeight(); const uint64_t flags = CIF_UsePartialAccessToPixelData; const uint64_t frameCount = 1; const uint64_t bufferSize = size_; std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(bufferSize); int read_index = pyramidSource_->getNextDicomDatasetReaderIndex(); DICOMDatasetReader *datasetReader = pyramidSource_->dicomDatasetReader(read_index); { boost::lock_guard<boost::mutex> guard(*datasetReader->datasetMutex()); DicomImage img(datasetReader->dataset(), pyramidSource_->transferSyntax(), flags, static_cast<uint64_t>(frameNumber_), frameCount); if (!img.getOutputData(buffer.get(), bufferSize)) { return 0; } } cv::Mat bgr(height, width, CV_8UC3, buffer.get()); cv::Mat bgra(height, width, CV_8UC4, rawMemory); cv::cvtColor(bgr, bgra, cv::COLOR_BGR2BGRA); return width * height * 4; } JpegDicomFileFrame::JpegDicomFileFrame(int64_t locationX, int64_t locationY, uint8_t *dicomMem, uint64_t dicomMemSize, DcmFilePyramidSource *pyramidSource): AbstractDicomFileFrame(locationX, locationY, pyramidSource), dicomFrameMemory_(dicomMem) { size_ = dicomMemSize; } J_COLOR_SPACE JpegDicomFileFrame::jpegDecodeColorSpace() const { return photoMetrInt() == "RGB" ? JCS_RGB : JCS_YCbCr; } int64_t JpegDicomFileFrame::rawABGRFrameBytes(uint8_t *rawMemory, int64_t memorySize) { const uint64_t width = frameWidth(); const uint64_t height = frameHeight(); if (jpegUtil::decodeJpeg(width, height, jpegDecodeColorSpace(), dicomFrameMemory_, size_, rawMemory, memorySize)) { return width * height * 4; } return 0; } Jp2KDicomFileFrame::Jp2KDicomFileFrame(int64_t locationX, int64_t locationY, uint8_t *dicomMem, uint64_t dicomMemSize, DcmFilePyramidSource *pyramidSource): AbstractDicomFileFrame(locationX, locationY, pyramidSource), dicomFrameMemory_(dicomMem) { size_ = dicomMemSize; } int64_t Jp2KDicomFileFrame::rawABGRFrameBytes(uint8_t *rawMemory, int64_t memorySize) { cv::Mat rawData(1, size_, CV_8UC1, reinterpret_cast<void*>(dicomFrameMemory_)); cv::Mat decodedImage = cv::imdecode(rawData, cv::IMREAD_COLOR); if ( decodedImage.data == NULL ) { return 0; } const uint64_t width = frameWidth(); const uint64_t height = frameHeight(); cv::Mat bgra(height, width, CV_8UC4, rawMemory); cv::cvtColor(decodedImage, bgra, cv::COLOR_RGB2BGRA); return width * height * 4; } DcmFilePyramidSource::DcmFilePyramidSource(absl::string_view filePath, bool loadframes): BaseFilePyramidSource<AbstractDicomFileFrame>(filePath) { errorMsg_ = ""; xfer_ = EXS_Unknown; dcmtkCodecRegistered_ = false; dimensionalOrganization_ = ""; studyInstanceUID_ = ""; seriesInstanceUID_ = ""; seriesDescription_ = ""; std::string filename = static_cast<std::string>(filePath); dcmFile_.loadFile(filename.c_str()); frameReaderIndex_ = 0; if (!loadframes) { maxFrameReaderIndex_ = 0; } else { maxFrameReaderIndex_ = 30; dicomDatasetSpeedReader_.reserve(maxFrameReaderIndex_); for (int idx = 0; idx < maxFrameReaderIndex_; ++idx) { std::unique_ptr<DICOMDatasetReader> reader = std::make_unique<DICOMDatasetReader>(filename); dicomDatasetSpeedReader_.push_back(std::move(reader)); } } dataset_ = dcmFile_.getDataset(); frameWidth_ = getTagValueUI16(DCM_Columns); if (frameWidth_ <= 0) { setErrorMsg("DICOM missing FrameWidth."); return; } frameHeight_ = getTagValueUI16(DCM_Rows); if (frameHeight_ <= 0) { setErrorMsg("DICOM missing FrameHeight."); return; } imageWidth_ = getTagValueUI32(DCM_TotalPixelMatrixColumns); if (imageWidth_ <= 0) { setErrorMsg("DICOM missing TotalPixelMatrixColumns."); return; } imageHeight_ = getTagValueUI32(DCM_TotalPixelMatrixRows); if (imageHeight_ <= 0) { setErrorMsg("DICOM missing TotalPixelMatrixRows."); return; } int64_t frameCount = getTagValueI64(DCM_NumberOfFrames); if (frameCount <= 0) { setErrorMsg("DICOM missing NumberOfFrames."); return; } photometric_ = getTagValueString(DCM_PhotometricInterpretation); if (photometric_ == "") { setErrorMsg("DICOM missing PhotometricInterpretation."); return; } samplesPerPixel_ = getTagValueUI16(DCM_SamplesPerPixel); if (samplesPerPixel_ <= 0) { setErrorMsg("DICOM missing SamplesPerPixel."); return; } planarConfiguration_ = getTagValueUI16(DCM_PlanarConfiguration); bitsAllocated_ = getTagValueUI16(DCM_BitsAllocated); if (bitsAllocated_ <= 0) { setErrorMsg("DICOM missing BitsAllocated."); return; } bitsStored_ = getTagValueUI16(DCM_BitsStored); if (bitsStored_ <= 0) { setErrorMsg("DICOM missing BitsStored."); return; } highBit_ = getTagValueUI16(DCM_HighBit); if (highBit_ <= 0) { setErrorMsg("DICOM missing HighBit."); return; } pixelRepresentation_ = getTagValueUI16(DCM_PixelRepresentation); firstLevelWidthMm_ = getTagValueFloat32(DCM_ImagedVolumeWidth); if (firstLevelWidthMm_ <= 0.0) { setErrorMsg("DICOM missing ImagedVolumeWidth."); return; } firstLevelHeightMm_ = getTagValueFloat32(DCM_ImagedVolumeHeight); if (firstLevelHeightMm_ <= 0.0) { setErrorMsg("DICOM missing ImagedVolumeHeight."); return; } if (tiledFull()) { uint64_t frames_per_row = imageWidth_ / frameWidth_; if (imageWidth_ % frameWidth_ > 0) { frames_per_row += 1; } uint64_t frames_per_column = imageHeight_ / frameHeight_; if (imageHeight_ % frameHeight_ > 0) { frames_per_column += 1; } if (frameCount != frames_per_row * frames_per_column) { setErrorMsg("Invalid number of frames in DICOM,"); return; } } dimensionalOrganization_ = getTagValueStringArray( DCM_DimensionOrganizationType); studyInstanceUID_ = getTagValueStringArray(DCM_StudyInstanceUID); seriesInstanceUID_ = getTagValueStringArray(DCM_SeriesInstanceUID); seriesDescription_ = getTagValueStringArray(DCM_SeriesDescription); // Get pixel data sequence DcmStack stack; if (!dataset_->search(DCM_PixelData, stack, ESM_fromHere, OFFalse).good()) { setErrorMsg("DICOM missing PixelData"); return; } DcmPixelData *pixelData = reinterpret_cast<DcmPixelData *>(stack.top()); if (pixelData == nullptr || !pixelData->verify().good() || !pixelData->checkValue().good()) { setErrorMsg("DICOM PixelData is invalid."); return; } const DcmRepresentationParameter *repParam = nullptr; pixelData->getOriginalRepresentationKey(xfer_, repParam); if (xfer_ == EXS_Unknown) { setErrorMsg("Unknown DICOM transfer syntax"); return; } if (!loadframes) { return; } framesData_.reserve(frameCount); bool DecodeLossyJPEG = false; bool DecodeJPEG2K = false; DcmPixelSequence *pixelSeq = nullptr; if (DcmXfer(xfer_).isPixelDataCompressed()) { if (!pixelData->getEncapsulatedRepresentation(xfer_, repParam, pixelSeq).good() || (pixelSeq == nullptr)) { setErrorMsg("Error getting pixel data."); return; } DecodeLossyJPEG = (EXS_JPEGProcess1 == xfer_ && 3 == samplesPerPixel_ && 0 == planarConfiguration_ && 0 == pixelRepresentation_ && 8 == bitsAllocated_ && 24 == bitsStored_ && 7 == highBit_ && (photometric_ == "RGB" || photometric_ == "YBR_FULL" || photometric_ == "YBR_FULL_422")); DecodeJPEG2K = (EXS_JPEG2000LosslessOnly == xfer_ || EXS_JPEG2000 == xfer_ || EXS_JPEG2000MulticomponentLosslessOnly == xfer_ || EXS_JPEG2000Multicomponent == xfer_); if (!DecodeLossyJPEG && !DecodeJPEG2K) { DJDecoderRegistration::registerCodecs(); DcmRLEDecoderRegistration::registerCodecs(); dcmtkCodecRegistered_ = true; } } int outputDicomImageSize = 0; if (!DecodeLossyJPEG && !DecodeJPEG2K) { const uint64_t flags = CIF_UsePartialAccessToPixelData; DicomImage img(dataset_, xfer_, flags, static_cast<uint64_t>(0), static_cast<uint64_t>(1)); outputDicomImageSize = img.getOutputDataSize(); if (outputDicomImageSize == 0) { setErrorMsg("Error getting output image size."); return; } } uint64_t locationX = 0; uint64_t locationY = 0; for (size_t idx = 0; idx < frameCount; ++idx) { if (locationX > imageWidth_) { locationX = 0; locationY += frameHeight_; } if (DecodeLossyJPEG || DecodeJPEG2K) { DcmPixelItem *pixelItem; if (!pixelSeq->getItem(pixelItem, idx+1).good()) { setErrorMsg("Error getting DICOM Frame."); return; } uint64_t dicomFrameMemorySize = pixelItem->getLength(); uint8_t *dicomFrameMemory; if (!pixelItem->getUint8Array(dicomFrameMemory).good()) { setErrorMsg("Error getting DICOM Frame Memory."); return; } if (DecodeLossyJPEG) { framesData_.push_back(std::make_unique<JpegDicomFileFrame>(locationX, locationY, dicomFrameMemory, dicomFrameMemorySize, this)); } else { framesData_.push_back(std::make_unique<Jp2KDicomFileFrame>(locationX, locationY, dicomFrameMemory, dicomFrameMemorySize, this)); } } else { framesData_.push_back(std::make_unique<DICOMImageFrame>(idx, locationX, locationY, outputDicomImageSize, this)); } locationX += frameWidth_; } BOOST_LOG_TRIVIAL(info) << "Done Queueing Frames"; } void DcmFilePyramidSource::setErrorMsg(absl::string_view msg) { errorMsg_ = static_cast<std::string>(msg); BOOST_LOG_TRIVIAL(error) << errorMsg_.c_str(); } bool DcmFilePyramidSource::isValid() const { return errorMsg_ == ""; } std::string DcmFilePyramidSource::errorMsg() const { return errorMsg_; } DICOMDatasetReader * DcmFilePyramidSource::dicomDatasetReader(int index) { return dicomDatasetSpeedReader_[index].get(); } int DcmFilePyramidSource::getNextDicomDatasetReaderIndex() { boost::lock_guard<boost::mutex> guard(*datasetMutex()); frameReaderIndex_ += 1; if (frameReaderIndex_ >= maxFrameReaderIndex_) { frameReaderIndex_ = 0; } return frameReaderIndex_; } DcmFilePyramidSource::~DcmFilePyramidSource() { if (dcmtkCodecRegistered_) { DJDecoderRegistration::cleanup(); DcmRLEDecoderRegistration::cleanup(); } } DcmDataset *DcmFilePyramidSource::dataset() { return dataset_; } boost::mutex* DcmFilePyramidSource::datasetMutex() { return &datasetMutex_; } bool DcmFilePyramidSource::tiledFull() const { return dimensionalOrganization_.find("TILED_FULL") != std::string::npos; } bool DcmFilePyramidSource::tiledSparse() const { return dimensionalOrganization_.find("TILED_SPARSE") != std::string::npos; } double DcmFilePyramidSource::getTagValueFloat32(const DcmTagKey &dcmTag) { Float32 value; if (dcmFile_.getDataset()->findAndGetFloat32(dcmTag, value, 0, OFFalse /*searchIntoSub*/).good()) { return value; } return 0.0; } int64_t DcmFilePyramidSource::getTagValueUI16(const DcmTagKey &dcmTag) { uint16_t value; if (dcmFile_.getDataset()->findAndGetUint16(dcmTag, value, 0, OFFalse /*searchIntoSub*/).good()) { return value; } return 0; } int64_t DcmFilePyramidSource::getTagValueUI32(const DcmTagKey &dcmTag) { uint32_t value; if (dcmFile_.getDataset()->findAndGetUint32(dcmTag, value, 0, OFFalse /*searchIntoSub*/).good()) { return value; } return 0; } int64_t DcmFilePyramidSource::getTagValueI64(const DcmTagKey &dcmTag) { int64_t value; if (dcmFile_.getDataset()->findAndGetLongInt(dcmTag, value, 0, OFFalse /*searchIntoSub*/).good()) { return value; } return 0; } std::string DcmFilePyramidSource::getTagValueString(const DcmTagKey &dcmTag) { OFString value; if (dcmFile_.getDataset()->findAndGetOFString(dcmTag, value, 0, OFFalse /*searchIntoSub*/).good()) { return value.c_str(); } return ""; } std::string DcmFilePyramidSource::getTagValueStringArray( const DcmTagKey &dcmTag) { OFString value; if (dcmFile_.getDataset()->findAndGetOFStringArray(dcmTag, value, OFFalse /*searchIntoSub*/).good()) { return value.c_str(); } return ""; } E_TransferSyntax DcmFilePyramidSource::transferSyntax() const { return xfer_; } DcmXfer DcmFilePyramidSource::transferSyntaxDcmXfer() const { return DcmXfer(xfer_); } absl::string_view DcmFilePyramidSource::studyInstanceUID() const { return studyInstanceUID_.c_str(); } absl::string_view DcmFilePyramidSource::seriesInstanceUID() const { return seriesInstanceUID_.c_str(); } absl::string_view DcmFilePyramidSource::seriesDescription() const { return seriesDescription_.c_str(); } void DcmFilePyramidSource::debugLog() const { std::string tile = ""; if (tiledFull()) { tile += "tile_full"; } if (tiledSparse()) { tile += "tile_sparse"; } if (tile == "") { tile = "unknown"; } BOOST_LOG_TRIVIAL(info) << "Image Dim: " << imageWidth() << ", " << imageHeight() << "\n" << "Dim mm: " << imageHeightMM() << ", " << imageWidthMM() << "\n" << "Downsample: " << downsample() << "\n" << "Photometric: " << static_cast<std::string>(photometricInterpretation()) << "\n" << "Frame Count: " << fileFrameCount() << "\n" << "Tile: " << tile << "\n" << "Frame Dim: " << frameWidth() << ", " << frameHeight() << "\n" << "Transfer Syntax: " << transferSyntax(); } } // namespace wsiToDicomConverter