Mercurial > hg > orthanc
diff OrthancServer/Internals/DicomFrameIndex.cpp @ 1924:6c73df12ca51
New URI: "/instances/.../frames/.../raw" to access the raw frames (bypass image decoding)
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 07 Mar 2016 17:43:20 +0100 |
parents | |
children | 84c7eaeb5244 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Internals/DicomFrameIndex.cpp Mon Mar 07 17:43:20 2016 +0100 @@ -0,0 +1,411 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeadersServer.h" +#include "DicomFrameIndex.h" + +#include "../../Core/OrthancException.h" +#include "../../Core/DicomFormat/DicomImageInformation.h" +#include "../FromDcmtkBridge.h" +#include "DicomImageDecoder.h" + +#include <boost/lexical_cast.hpp> + +#include <dcmtk/dcmdata/dcdeftag.h> +#include <dcmtk/dcmdata/dcpxitem.h> +#include <dcmtk/dcmdata/dcpixseq.h> + +#if defined(_WIN32) // Windows machines are always little-endian +# define le32toh(x) x +#elif defined(__APPLE__) +# include <libkern/OSByteOrder.h> +# define le32toh(x) OSSwapLittleToHostInt32(x) +#else +# include <endian.h> +#endif + + +namespace Orthanc +{ + class DicomFrameIndex::FragmentIndex : public DicomFrameIndex::IIndex + { + private: + DcmPixelSequence* pixelSequence_; + std::vector<DcmPixelItem*> startFragment_; + std::vector<unsigned int> countFragments_; + std::vector<unsigned int> frameSize_; + + void GetOffsetTable(std::vector<uint32_t>& table) + { + DcmPixelItem* item = NULL; + if (!pixelSequence_->getItem(item, 0).good() || + item == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + uint32_t length = item->getLength(); + if (length == 0) + { + table.clear(); + return; + } + + if (length % 4 != 0) + { + // Error: Each fragment is index with 4 bytes (uint32_t) + throw OrthancException(ErrorCode_BadFileFormat); + } + + uint8_t* content = NULL; + if (!item->getUint8Array(content).good() || + content == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + table.resize(length / 4); + + // The offset table is always in little endian in the DICOM + // file. Swap it to host endianness if needed. + const uint32_t* offset = reinterpret_cast<const uint32_t*>(content); + for (size_t i = 0; i < table.size(); i++, offset++) + { + table[i] = le32toh(*offset); + } + } + + + public: + FragmentIndex(DcmPixelSequence* pixelSequence, + unsigned int countFrames) : + pixelSequence_(pixelSequence) + { + assert(pixelSequence != NULL); + + startFragment_.resize(countFrames); + countFragments_.resize(countFrames); + frameSize_.resize(countFrames); + + // The first fragment corresponds to the offset table + unsigned int countFragments = static_cast<unsigned int>(pixelSequence_->card()); + if (countFragments < countFrames + 1) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + if (countFragments == countFrames + 1) + { + // Simple case: There is one fragment per frame. + + DcmObject* fragment = pixelSequence_->nextInContainer(NULL); // Skip the offset table + if (fragment == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + for (unsigned int i = 0; i < countFrames; i++) + { + fragment = pixelSequence_->nextInContainer(fragment); + startFragment_[i] = dynamic_cast<DcmPixelItem*>(fragment); + frameSize_[i] = fragment->getLength(); + countFragments_[i] = 1; + } + + return; + } + + // Parse the offset table + std::vector<uint32_t> offsetOfFrame; + GetOffsetTable(offsetOfFrame); + + if (offsetOfFrame.size() != countFrames || + offsetOfFrame[0] != 0) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + + // Loop over the fragments (ignoring the offset table). This is + // an alternative, faster implementation to DCMTK's + // "DcmCodec::determineStartFragment()". + DcmObject* fragment = pixelSequence_->nextInContainer(NULL); + if (fragment == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + fragment = pixelSequence_->nextInContainer(fragment); // Skip the offset table + if (fragment == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + uint32_t offset = 0; + unsigned int currentFrame = 0; + startFragment_[0] = dynamic_cast<DcmPixelItem*>(fragment); + + unsigned int currentFragment = 1; + while (fragment != NULL) + { + if (currentFrame + 1 < countFrames && + offset == offsetOfFrame[currentFrame + 1]) + { + currentFrame += 1; + startFragment_[currentFrame] = dynamic_cast<DcmPixelItem*>(fragment); + } + + frameSize_[currentFrame] += fragment->getLength(); + countFragments_[currentFrame]++; + + // 8 bytes = overhead for the item tag and length field + offset += fragment->getLength() + 8; + + currentFragment++; + fragment = pixelSequence_->nextInContainer(fragment); + } + + if (currentFragment != countFragments || + currentFrame + 1 != countFrames || + fragment != NULL) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + assert(startFragment_.size() == countFragments_.size() && + startFragment_.size() == frameSize_.size()); + } + + + virtual void GetRawFrame(std::string& frame, + unsigned int index) const + { + if (index >= startFragment_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + frame.resize(frameSize_[index]); + if (frame.size() == 0) + { + return; + } + + uint8_t* target = reinterpret_cast<uint8_t*>(&frame[0]); + + size_t offset = 0; + DcmPixelItem* fragment = startFragment_[index]; + for (unsigned int i = 0; i < countFragments_[index]; i++) + { + uint8_t* content = NULL; + if (!fragment->getUint8Array(content).good() || + content == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + assert(offset + fragment->getLength() <= frame.size()); + + memcpy(target + offset, content, fragment->getLength()); + offset += fragment->getLength(); + + fragment = dynamic_cast<DcmPixelItem*>(pixelSequence_->nextInContainer(fragment)); + } + } + }; + + + + class DicomFrameIndex::UncompressedIndex : public DicomFrameIndex::IIndex + { + private: + uint8_t* pixelData_; + size_t frameSize_; + + public: + UncompressedIndex(DcmDataset& dataset, + unsigned int countFrames, + size_t frameSize) : + pixelData_(NULL), + frameSize_(frameSize) + { + size_t size = 0; + + DcmElement* e; + if (dataset.findAndGetElement(DCM_PixelData, e).good() && + e != NULL) + { + size = e->getLength(); + + if (size > 0) + { + pixelData_ = NULL; + if (!e->getUint8Array(pixelData_).good() || + pixelData_ == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + } + + if (size < frameSize_ * countFrames) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + virtual void GetRawFrame(std::string& frame, + unsigned int index) const + { + frame.resize(frameSize_); + if (frameSize_ > 0) + { + memcpy(&frame[0], pixelData_ + index * frameSize_, frameSize_); + } + } + }; + + + class DicomFrameIndex::PsmctRle1Index : public DicomFrameIndex::IIndex + { + private: + std::string pixelData_; + size_t frameSize_; + + public: + PsmctRle1Index(DcmDataset& dataset, + unsigned int countFrames, + size_t frameSize) : + frameSize_(frameSize) + { + if (!DicomImageDecoder::DecodePsmctRle1(pixelData_, dataset) || + pixelData_.size() < frameSize * countFrames) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + virtual void GetRawFrame(std::string& frame, + unsigned int index) const + { + frame.resize(frameSize_); + if (frameSize_ > 0) + { + memcpy(&frame[0], reinterpret_cast<const uint8_t*>(&pixelData_[0]) + index * frameSize_, frameSize_); + } + } + }; + + + + + unsigned int DicomFrameIndex::GetFramesCount(DcmDataset& dataset) + { + const char* tmp = NULL; + if (!dataset.findAndGetString(DCM_NumberOfFrames, tmp).good() || + tmp == NULL) + { + return 1; + } + + int count = -1; + try + { + count = boost::lexical_cast<int>(tmp); + } + catch (boost::bad_lexical_cast&) + { + } + + if (count < 0) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + return count; + } + } + + + DicomFrameIndex::DicomFrameIndex(DcmDataset& dataset) + { + countFrames_ = GetFramesCount(dataset); + if (countFrames_ == 0) + { + // The image has no frame. No index is to be built. + return; + } + + // Test whether this image is composed of a sequence of fragments + DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset); + if (pixelSequence != NULL) + { + index_.reset(new FragmentIndex(pixelSequence, countFrames_)); + return; + } + + // Extract information about the image structure + DicomMap tags; + FromDcmtkBridge::Convert(tags, dataset); + + DicomImageInformation information(tags); + + // Access to the raw pixel data + if (DicomImageDecoder::IsPsmctRle1(dataset)) + { + index_.reset(new PsmctRle1Index(dataset, countFrames_, information.GetFrameSize())); + } + else + { + index_.reset(new UncompressedIndex(dataset, countFrames_, information.GetFrameSize())); + } + } + + + void DicomFrameIndex::GetRawFrame(std::string& frame, + unsigned int index) const + { + if (index >= countFrames_) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else if (index_.get() != NULL) + { + return index_->GetRawFrame(frame, index); + } + else + { + frame.clear(); + } + } +}