Mercurial > hg > orthanc
diff OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp @ 4044:d25f4c0fa160 framework
splitting code into OrthancFramework and OrthancServer
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 10 Jun 2020 20:30:34 +0200 |
parents | Core/DicomParsing/ParsedDicomFile.cpp@fa35465175b8 |
children | bf7b9edf6b81 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp Wed Jun 10 20:30:34 2020 +0200 @@ -0,0 +1,1742 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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/>. + **/ + + + +/*========================================================================= + + This file is based on portions of the following project: + + Program: GDCM (Grassroots DICOM). A DICOM library + Module: http://gdcm.sourceforge.net/Copyright.html + + Copyright (c) 2006-2011 Mathieu Malaterre + Copyright (c) 1993-2005 CREATIS + (CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither name of Mathieu Malaterre, or CREATIS, nor the names of any + contributors (CNRS, INSERM, UCB, Universite Lyon I), may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + =========================================================================*/ + + +#include "../PrecompiledHeaders.h" + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include "ParsedDicomFile.h" + +#include "FromDcmtkBridge.h" +#include "Internals/DicomFrameIndex.h" +#include "ToDcmtkBridge.h" + +#include "../Images/PamReader.h" +#include "../Logging.h" +#include "../OrthancException.h" +#include "../Toolbox.h" + +#if ORTHANC_SANDBOXED == 0 +# include "../SystemToolbox.h" +#endif + +#if ORTHANC_ENABLE_JPEG == 1 +# include "../Images/JpegReader.h" +#endif + +#if ORTHANC_ENABLE_PNG == 1 +# include "../Images/PngReader.h" +#endif + +#include <list> +#include <limits> + +#include <boost/lexical_cast.hpp> + +#include <dcmtk/dcmdata/dcchrstr.h> +#include <dcmtk/dcmdata/dcdicent.h> +#include <dcmtk/dcmdata/dcdict.h> +#include <dcmtk/dcmdata/dcfilefo.h> +#include <dcmtk/dcmdata/dcuid.h> +#include <dcmtk/dcmdata/dcmetinf.h> +#include <dcmtk/dcmdata/dcdeftag.h> + +#include <dcmtk/dcmdata/dcvrae.h> +#include <dcmtk/dcmdata/dcvras.h> +#include <dcmtk/dcmdata/dcvrcs.h> +#include <dcmtk/dcmdata/dcvrda.h> +#include <dcmtk/dcmdata/dcvrds.h> +#include <dcmtk/dcmdata/dcvrdt.h> +#include <dcmtk/dcmdata/dcvrfd.h> +#include <dcmtk/dcmdata/dcvrfl.h> +#include <dcmtk/dcmdata/dcvris.h> +#include <dcmtk/dcmdata/dcvrlo.h> +#include <dcmtk/dcmdata/dcvrlt.h> +#include <dcmtk/dcmdata/dcvrpn.h> +#include <dcmtk/dcmdata/dcvrsh.h> +#include <dcmtk/dcmdata/dcvrsl.h> +#include <dcmtk/dcmdata/dcvrss.h> +#include <dcmtk/dcmdata/dcvrst.h> +#include <dcmtk/dcmdata/dcvrtm.h> +#include <dcmtk/dcmdata/dcvrui.h> +#include <dcmtk/dcmdata/dcvrul.h> +#include <dcmtk/dcmdata/dcvrus.h> +#include <dcmtk/dcmdata/dcvrut.h> +#include <dcmtk/dcmdata/dcpixel.h> +#include <dcmtk/dcmdata/dcpixseq.h> +#include <dcmtk/dcmdata/dcpxitem.h> + + +#include <boost/math/special_functions/round.hpp> +#include <dcmtk/dcmdata/dcostrmb.h> +#include <boost/algorithm/string/predicate.hpp> + + +#if DCMTK_VERSION_NUMBER <= 360 +# define EXS_JPEGProcess1 EXS_JPEGProcess1TransferSyntax +#endif + + + +namespace Orthanc +{ + struct ParsedDicomFile::PImpl + { + std::unique_ptr<DcmFileFormat> file_; + std::unique_ptr<DicomFrameIndex> frameIndex_; + }; + + +#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 + static void ParseTagAndGroup(DcmTagKey& key, + const std::string& tag) + { + DicomTag t = FromDcmtkBridge::ParseTag(tag); + key = DcmTagKey(t.GetGroup(), t.GetElement()); + } + + + static unsigned int GetPixelDataBlockCount(DcmPixelData& pixelData, + E_TransferSyntax transferSyntax) + { + DcmPixelSequence* pixelSequence = NULL; + if (pixelData.getEncapsulatedRepresentation + (transferSyntax, NULL, pixelSequence).good() && pixelSequence) + { + return pixelSequence->card(); + } + else + { + return 1; + } + } + + + static void SendPathValueForDictionary(RestApiOutput& output, + DcmItem& dicom) + { + Json::Value v = Json::arrayValue; + + for (unsigned long i = 0; i < dicom.card(); i++) + { + DcmElement* element = dicom.getElement(i); + if (element) + { + char buf[16]; + sprintf(buf, "%04x-%04x", element->getTag().getGTag(), element->getTag().getETag()); + v.append(buf); + } + } + + output.AnswerJson(v); + } + + + static void SendSequence(RestApiOutput& output, + DcmSequenceOfItems& sequence) + { + // This element is a sequence + Json::Value v = Json::arrayValue; + + for (unsigned long i = 0; i < sequence.card(); i++) + { + v.append(boost::lexical_cast<std::string>(i)); + } + + output.AnswerJson(v); + } + + + namespace + { + class DicomFieldStream : public IHttpStreamAnswer + { + private: + DcmElement& element_; + uint32_t length_; + uint32_t offset_; + std::string chunk_; + size_t chunkSize_; + + public: + DicomFieldStream(DcmElement& element, + E_TransferSyntax transferSyntax) : + element_(element), + length_(element.getLength(transferSyntax)), + offset_(0), + chunkSize_(0) + { + static const size_t CHUNK_SIZE = 64 * 1024; // Use chunks of max 64KB + chunk_.resize(CHUNK_SIZE); + } + + virtual HttpCompression SetupHttpCompression(bool /*gzipAllowed*/, + bool /*deflateAllowed*/) + ORTHANC_OVERRIDE + { + // No support for compression + return HttpCompression_None; + } + + virtual bool HasContentFilename(std::string& filename) ORTHANC_OVERRIDE + { + return false; + } + + virtual std::string GetContentType() ORTHANC_OVERRIDE + { + return EnumerationToString(MimeType_Binary); + } + + virtual uint64_t GetContentLength() ORTHANC_OVERRIDE + { + return length_; + } + + virtual bool ReadNextChunk() ORTHANC_OVERRIDE + { + assert(offset_ <= length_); + + if (offset_ == length_) + { + return false; + } + else + { + if (length_ - offset_ < chunk_.size()) + { + chunkSize_ = length_ - offset_; + } + else + { + chunkSize_ = chunk_.size(); + } + + OFCondition cond = element_.getPartialValue(&chunk_[0], offset_, chunkSize_); + + offset_ += chunkSize_; + + if (!cond.good()) + { + throw OrthancException(ErrorCode_InternalError, + "Error while sending a DICOM field: " + + std::string(cond.text())); + } + + return true; + } + } + + virtual const char *GetChunkContent() ORTHANC_OVERRIDE + { + return chunk_.c_str(); + } + + virtual size_t GetChunkSize() ORTHANC_OVERRIDE + { + return chunkSize_; + } + }; + } + + + static bool AnswerPixelData(RestApiOutput& output, + DcmItem& dicom, + E_TransferSyntax transferSyntax, + const std::string* blockUri) + { + DcmTag k(DICOM_TAG_PIXEL_DATA.GetGroup(), + DICOM_TAG_PIXEL_DATA.GetElement()); + + DcmElement *element = NULL; + if (!dicom.findAndGetElement(k, element).good() || + element == NULL) + { + return false; + } + + try + { + DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element); + if (blockUri == NULL) + { + // The user asks how many blocks are present in this pixel data + unsigned int blocks = GetPixelDataBlockCount(pixelData, transferSyntax); + + Json::Value result(Json::arrayValue); + for (unsigned int i = 0; i < blocks; i++) + { + result.append(boost::lexical_cast<std::string>(i)); + } + + output.AnswerJson(result); + return true; + } + + + unsigned int block = boost::lexical_cast<unsigned int>(*blockUri); + + if (block < GetPixelDataBlockCount(pixelData, transferSyntax)) + { + DcmPixelSequence* pixelSequence = NULL; + if (pixelData.getEncapsulatedRepresentation + (transferSyntax, NULL, pixelSequence).good() && pixelSequence) + { + // This is the case for JPEG transfer syntaxes + if (block < pixelSequence->card()) + { + DcmPixelItem* pixelItem = NULL; + if (pixelSequence->getItem(pixelItem, block).good() && pixelItem) + { + if (pixelItem->getLength() == 0) + { + output.AnswerBuffer(NULL, 0, MimeType_Binary); + return true; + } + + Uint8* buffer = NULL; + if (pixelItem->getUint8Array(buffer).good() && buffer) + { + output.AnswerBuffer(buffer, pixelItem->getLength(), MimeType_Binary); + return true; + } + } + } + } + else + { + // This is the case for raw, uncompressed image buffers + assert(*blockUri == "0"); + DicomFieldStream stream(*element, transferSyntax); + output.AnswerStream(stream); + } + } + } + catch (boost::bad_lexical_cast&) + { + // The URI entered by the user is not a number + } + catch (std::bad_cast&) + { + // This should never happen + } + + return false; + } + + + static void SendPathValueForLeaf(RestApiOutput& output, + const std::string& tag, + DcmItem& dicom, + E_TransferSyntax transferSyntax) + { + DcmTagKey k; + ParseTagAndGroup(k, tag); + + DcmSequenceOfItems* sequence = NULL; + if (dicom.findAndGetSequence(k, sequence).good() && + sequence != NULL && + sequence->getVR() == EVR_SQ) + { + SendSequence(output, *sequence); + return; + } + + DcmElement* element = NULL; + if (dicom.findAndGetElement(k, element).good() && + element != NULL && + //element->getVR() != EVR_UNKNOWN && // This would forbid private tags + element->getVR() != EVR_SQ) + { + DicomFieldStream stream(*element, transferSyntax); + output.AnswerStream(stream); + } + } +#endif + + + static inline uint16_t GetCharValue(char c) + { + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else + return 0; + } + + + static inline uint16_t GetTagValue(const char* c) + { + return ((GetCharValue(c[0]) << 12) + + (GetCharValue(c[1]) << 8) + + (GetCharValue(c[2]) << 4) + + GetCharValue(c[3])); + } + + +#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 + void ParsedDicomFile::SendPathValue(RestApiOutput& output, + const UriComponents& uri) + { + DcmItem* dicom = GetDcmtkObject().getDataset(); + E_TransferSyntax transferSyntax = GetDcmtkObject().getDataset()->getCurrentXfer(); + + // Special case: Accessing the pixel data + if (uri.size() == 1 || + uri.size() == 2) + { + DcmTagKey tag; + ParseTagAndGroup(tag, uri[0]); + + if (tag.getGroup() == DICOM_TAG_PIXEL_DATA.GetGroup() && + tag.getElement() == DICOM_TAG_PIXEL_DATA.GetElement()) + { + AnswerPixelData(output, *dicom, transferSyntax, uri.size() == 1 ? NULL : &uri[1]); + return; + } + } + + // Go down in the tag hierarchy according to the URI + for (size_t pos = 0; pos < uri.size() / 2; pos++) + { + size_t index; + try + { + index = boost::lexical_cast<size_t>(uri[2 * pos + 1]); + } + catch (boost::bad_lexical_cast&) + { + return; + } + + DcmTagKey k; + DcmItem *child = NULL; + ParseTagAndGroup(k, uri[2 * pos]); + if (!dicom->findAndGetSequenceItem(k, child, index).good() || + child == NULL) + { + return; + } + + dicom = child; + } + + // We have reached the end of the URI + if (uri.size() % 2 == 0) + { + SendPathValueForDictionary(output, *dicom); + } + else + { + SendPathValueForLeaf(output, uri.back(), *dicom, transferSyntax); + } + } +#endif + + + void ParsedDicomFile::Remove(const DicomTag& tag) + { + InvalidateCache(); + + DcmTagKey key(tag.GetGroup(), tag.GetElement()); + DcmElement* element = GetDcmtkObject().getDataset()->remove(key); + if (element != NULL) + { + delete element; + } + } + + + void ParsedDicomFile::Clear(const DicomTag& tag, + bool onlyIfExists) + { + if (tag.GetElement() == 0x0000) + { + // Prevent manually modifying generic group length tags: This is + // handled by DCMTK serialization + return; + } + + InvalidateCache(); + + DcmItem* dicom = GetDcmtkObject().getDataset(); + DcmTagKey key(tag.GetGroup(), tag.GetElement()); + + if (onlyIfExists && + !dicom->tagExists(key)) + { + // The tag is non-existing, do not clear it + } + else + { + if (!dicom->insertEmptyElement(key, OFTrue /* replace old value */).good()) + { + throw OrthancException(ErrorCode_InternalError); + } + } + } + + + void ParsedDicomFile::RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep) + { + InvalidateCache(); + + DcmDataset& dataset = *GetDcmtkObject().getDataset(); + + // Loop over the dataset to detect its private tags + typedef std::list<DcmElement*> Tags; + Tags privateTags; + + for (unsigned long i = 0; i < dataset.card(); i++) + { + DcmElement* element = dataset.getElement(i); + DcmTag tag(element->getTag()); + + // Is this a private tag? + if (tag.isPrivate()) + { + bool remove = true; + + // Check whether this private tag is to be kept + if (toKeep != NULL) + { + DicomTag tmp = FromDcmtkBridge::Convert(tag); + if (toKeep->find(tmp) != toKeep->end()) + { + remove = false; // Keep it + } + } + + if (remove) + { + privateTags.push_back(element); + } + } + } + + // Loop over the detected private tags to remove them + for (Tags::iterator it = privateTags.begin(); + it != privateTags.end(); ++it) + { + DcmElement* tmp = dataset.remove(*it); + if (tmp != NULL) + { + delete tmp; + } + } + } + + + static void InsertInternal(DcmDataset& dicom, + DcmElement* element) + { + OFCondition cond = dicom.insert(element, false, false); + if (!cond.good()) + { + // This field already exists + delete element; + throw OrthancException(ErrorCode_InternalError); + } + } + + + void ParsedDicomFile::Insert(const DicomTag& tag, + const Json::Value& value, + bool decodeDataUriScheme, + const std::string& privateCreator) + { + if (tag.GetElement() == 0x0000) + { + // Prevent manually modifying generic group length tags: This is + // handled by DCMTK serialization + return; + } + + if (GetDcmtkObject().getDataset()->tagExists(ToDcmtkBridge::Convert(tag))) + { + throw OrthancException(ErrorCode_AlreadyExistingTag); + } + + if (decodeDataUriScheme && + value.type() == Json::stringValue && + (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT || + tag == DICOM_TAG_PIXEL_DATA)) + { + if (EmbedContentInternal(value.asString())) + { + return; + } + } + + InvalidateCache(); + + bool hasCodeExtensions; + Encoding encoding = DetectEncoding(hasCodeExtensions); + std::unique_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator)); + InsertInternal(*GetDcmtkObject().getDataset(), element.release()); + } + + + void ParsedDicomFile::ReplacePlainString(const DicomTag& tag, + const std::string& utf8Value) + { + if (tag.IsPrivate()) + { + throw OrthancException(ErrorCode_InternalError, + "Cannot apply this function to private tags: " + tag.Format()); + } + else + { + Replace(tag, utf8Value, false, DicomReplaceMode_InsertIfAbsent, + "" /* not a private tag, so no private creator */); + } + } + + + void ParsedDicomFile::SetIfAbsent(const DicomTag& tag, + const std::string& utf8Value) + { + std::string currentValue; + if (!GetTagValue(currentValue, tag)) + { + ReplacePlainString(tag, utf8Value); + } + } + + + static bool CanReplaceProceed(DcmDataset& dicom, + const DcmTagKey& tag, + DicomReplaceMode mode) + { + if (dicom.findAndDeleteElement(tag).good()) + { + // This tag was existing, it has been deleted + return true; + } + else + { + // This tag was absent, act wrt. the specified "mode" + switch (mode) + { + case DicomReplaceMode_InsertIfAbsent: + return true; + + case DicomReplaceMode_ThrowIfAbsent: + throw OrthancException(ErrorCode_InexistentItem); + + case DicomReplaceMode_IgnoreIfAbsent: + return false; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + } + + + void ParsedDicomFile::UpdateStorageUid(const DicomTag& tag, + const std::string& utf8Value, + bool decodeDataUriScheme) + { + if (tag != DICOM_TAG_SOP_CLASS_UID && + tag != DICOM_TAG_SOP_INSTANCE_UID) + { + return; + } + + std::string binary; + const std::string* decoded = &utf8Value; + + if (decodeDataUriScheme && + boost::starts_with(utf8Value, URI_SCHEME_PREFIX_BINARY)) + { + std::string mime; + if (!Toolbox::DecodeDataUriScheme(mime, binary, utf8Value)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + decoded = &binary; + } + else + { + bool hasCodeExtensions; + Encoding encoding = DetectEncoding(hasCodeExtensions); + if (encoding != Encoding_Utf8) + { + binary = Toolbox::ConvertFromUtf8(utf8Value, encoding); + decoded = &binary; + } + } + + /** + * dcmodify will automatically correct 'Media Storage SOP Class + * UID' and 'Media Storage SOP Instance UID' in the metaheader, if + * you make changes to the related tags in the dataset ('SOP Class + * UID' and 'SOP Instance UID') via insert or modify mode + * options. You can disable this behaviour by using the -nmu + * option. + **/ + + if (tag == DICOM_TAG_SOP_CLASS_UID) + { + ReplacePlainString(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, *decoded); + } + + if (tag == DICOM_TAG_SOP_INSTANCE_UID) + { + ReplacePlainString(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, *decoded); + } + } + + + void ParsedDicomFile::Replace(const DicomTag& tag, + const std::string& utf8Value, + bool decodeDataUriScheme, + DicomReplaceMode mode, + const std::string& privateCreator) + { + if (tag.GetElement() == 0x0000) + { + // Prevent manually modifying generic group length tags: This is + // handled by DCMTK serialization + return; + } + + InvalidateCache(); + + DcmDataset& dicom = *GetDcmtkObject().getDataset(); + if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode)) + { + // Either the tag was previously existing (and now removed), or + // the replace mode was set to "InsertIfAbsent" + + if (decodeDataUriScheme && + (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT || + tag == DICOM_TAG_PIXEL_DATA)) + { + if (EmbedContentInternal(utf8Value)) + { + return; + } + } + + std::unique_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag, privateCreator)); + + if (!utf8Value.empty()) + { + bool hasCodeExtensions; + Encoding encoding = DetectEncoding(hasCodeExtensions); + FromDcmtkBridge::FillElementWithString(*element, utf8Value, decodeDataUriScheme, encoding); + } + + InsertInternal(dicom, element.release()); + UpdateStorageUid(tag, utf8Value, false); + } + } + + + void ParsedDicomFile::Replace(const DicomTag& tag, + const Json::Value& value, + bool decodeDataUriScheme, + DicomReplaceMode mode, + const std::string& privateCreator) + { + if (tag.GetElement() == 0x0000) + { + // Prevent manually modifying generic group length tags: This is + // handled by DCMTK serialization + return; + } + + InvalidateCache(); + + DcmDataset& dicom = *GetDcmtkObject().getDataset(); + if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode)) + { + // Either the tag was previously existing (and now removed), or + // the replace mode was set to "InsertIfAbsent" + + if (decodeDataUriScheme && + value.type() == Json::stringValue && + (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT || + tag == DICOM_TAG_PIXEL_DATA)) + { + if (EmbedContentInternal(value.asString())) + { + return; + } + } + + bool hasCodeExtensions; + Encoding encoding = DetectEncoding(hasCodeExtensions); + InsertInternal(dicom, FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator)); + + if (tag == DICOM_TAG_SOP_CLASS_UID || + tag == DICOM_TAG_SOP_INSTANCE_UID) + { + if (value.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + UpdateStorageUid(tag, value.asString(), decodeDataUriScheme); + } + } + } + + +#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 + void ParsedDicomFile::Answer(RestApiOutput& output) + { + std::string serialized; + if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, *GetDcmtkObject().getDataset())) + { + output.AnswerBuffer(serialized, MimeType_Dicom); + } + } +#endif + + + bool ParsedDicomFile::GetTagValue(std::string& value, + const DicomTag& tag) + { + DcmTagKey k(tag.GetGroup(), tag.GetElement()); + DcmDataset& dataset = *GetDcmtkObject().getDataset(); + + if (tag.IsPrivate() || + FromDcmtkBridge::IsUnknownTag(tag) || + tag == DICOM_TAG_PIXEL_DATA || + tag == DICOM_TAG_ENCAPSULATED_DOCUMENT) + { + const Uint8* data = NULL; // This is freed in the destructor of the dataset + long unsigned int count = 0; + + if (dataset.findAndGetUint8Array(k, data, &count).good()) + { + if (count > 0) + { + assert(data != NULL); + value.assign(reinterpret_cast<const char*>(data), count); + } + else + { + value.clear(); + } + + return true; + } + else + { + return false; + } + } + else + { + DcmElement* element = NULL; + if (!dataset.findAndGetElement(k, element).good() || + element == NULL) + { + return false; + } + + bool hasCodeExtensions; + Encoding encoding = DetectEncoding(hasCodeExtensions); + + std::set<DicomTag> tmp; + std::unique_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement + (*element, DicomToJsonFlags_Default, + 0, encoding, hasCodeExtensions, tmp)); + + if (v.get() == NULL || + v->IsNull()) + { + value = ""; + } + else + { + // TODO v->IsBinary() + value = v->GetContent(); + } + + return true; + } + } + + + DicomInstanceHasher ParsedDicomFile::GetHasher() + { + std::string patientId, studyUid, seriesUid, instanceUid; + + if (!GetTagValue(patientId, DICOM_TAG_PATIENT_ID)) + { + /** + * If "PatientID" is absent, be tolerant by considering it + * equals the empty string, then proceed. In Orthanc <= 1.5.6, + * an exception "Bad file format" was generated. + * https://groups.google.com/d/msg/orthanc-users/aphG_h1AHVg/rfOTtTPTAgAJ + * https://hg.orthanc-server.com/orthanc/rev/4c45e018bd3de3cfa21d6efc6734673aaaee4435 + **/ + patientId.clear(); + } + + if (!GetTagValue(studyUid, DICOM_TAG_STUDY_INSTANCE_UID) || + !GetTagValue(seriesUid, DICOM_TAG_SERIES_INSTANCE_UID) || + !GetTagValue(instanceUid, DICOM_TAG_SOP_INSTANCE_UID)) + { + throw OrthancException(ErrorCode_BadFileFormat, "missing StudyInstanceUID, SeriesInstanceUID or SOPInstanceUID"); + } + + return DicomInstanceHasher(patientId, studyUid, seriesUid, instanceUid); + } + + + void ParsedDicomFile::SaveToMemoryBuffer(std::string& buffer) + { + FromDcmtkBridge::SaveToMemoryBuffer(buffer, *GetDcmtkObject().getDataset()); + } + + +#if ORTHANC_SANDBOXED == 0 + void ParsedDicomFile::SaveToFile(const std::string& path) + { + // TODO Avoid using a temporary memory buffer, write directly on disk + std::string content; + SaveToMemoryBuffer(content); + SystemToolbox::WriteFile(content, path); + } +#endif + + + ParsedDicomFile::ParsedDicomFile(bool createIdentifiers) : pimpl_(new PImpl) + { + pimpl_->file_.reset(new DcmFileFormat); + + if (createIdentifiers) + { + ReplacePlainString(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient)); + ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study)); + ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series)); + ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance)); + } + } + + + void ParsedDicomFile::CreateFromDicomMap(const DicomMap& source, + Encoding defaultEncoding, + bool permissive, + const std::string& defaultPrivateCreator, + const std::map<uint16_t, std::string>& privateCreators) + { + pimpl_->file_.reset(new DcmFileFormat); + InvalidateCache(); + + const DicomValue* tmp = source.TestAndGetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET); + + if (tmp == NULL) + { + SetEncoding(defaultEncoding); + } + else if (tmp->IsBinary()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange, + "Invalid binary string in the SpecificCharacterSet (0008,0005) tag"); + } + else if (tmp->IsNull() || + tmp->GetContent().empty()) + { + SetEncoding(defaultEncoding); + } + else + { + Encoding encoding; + + if (GetDicomEncoding(encoding, tmp->GetContent().c_str())) + { + SetEncoding(encoding); + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange, + "Unsupported value for the SpecificCharacterSet (0008,0005) tag: \"" + + tmp->GetContent() + "\""); + } + } + + for (DicomMap::Content::const_iterator + it = source.content_.begin(); it != source.content_.end(); ++it) + { + if (it->first != DICOM_TAG_SPECIFIC_CHARACTER_SET && + !it->second->IsNull()) + { + try + { + // Same as "ReplacePlainString()", but with support for private creator + const std::string& utf8Value = it->second->GetContent(); + + std::map<uint16_t, std::string>::const_iterator found = privateCreators.find(it->first.GetGroup()); + + if (it->first.IsPrivate() && + found != privateCreators.end()) + { + Replace(it->first, utf8Value, false, DicomReplaceMode_InsertIfAbsent, found->second); + } + else + { + Replace(it->first, utf8Value, false, DicomReplaceMode_InsertIfAbsent, defaultPrivateCreator); + } + } + catch (OrthancException&) + { + if (!permissive) + { + throw; + } + } + } + } + } + + ParsedDicomFile::ParsedDicomFile(const DicomMap& map, + Encoding defaultEncoding, + bool permissive) : + pimpl_(new PImpl) + { + std::map<uint16_t, std::string> noPrivateCreators; + CreateFromDicomMap(map, defaultEncoding, permissive, "" /* no default private creator */, noPrivateCreators); + } + + + ParsedDicomFile::ParsedDicomFile(const DicomMap& map, + Encoding defaultEncoding, + bool permissive, + const std::string& defaultPrivateCreator, + const std::map<uint16_t, std::string>& privateCreators) : + pimpl_(new PImpl) + { + CreateFromDicomMap(map, defaultEncoding, permissive, defaultPrivateCreator, privateCreators); + } + + + ParsedDicomFile::ParsedDicomFile(const void* content, + size_t size) : pimpl_(new PImpl) + { + pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(content, size)); + } + + ParsedDicomFile::ParsedDicomFile(const std::string& content) : pimpl_(new PImpl) + { + if (content.size() == 0) + { + pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(NULL, 0)); + } + else + { + pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(&content[0], content.size())); + } + } + + + ParsedDicomFile::ParsedDicomFile(ParsedDicomFile& other, + bool keepSopInstanceUid) : + pimpl_(new PImpl) + { + pimpl_->file_.reset(dynamic_cast<DcmFileFormat*>(other.GetDcmtkObject().clone())); + + if (!keepSopInstanceUid) + { + // Create a new instance-level identifier + ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance)); + } + } + + + ParsedDicomFile::ParsedDicomFile(DcmDataset& dicom) : pimpl_(new PImpl) + { + pimpl_->file_.reset(new DcmFileFormat(&dicom)); + } + + + ParsedDicomFile::ParsedDicomFile(DcmFileFormat& dicom) : pimpl_(new PImpl) + { + pimpl_->file_.reset(new DcmFileFormat(dicom)); + } + + + ParsedDicomFile::ParsedDicomFile(DcmFileFormat* dicom) : pimpl_(new PImpl) + { + pimpl_->file_.reset(dicom); // No cloning + } + + + DcmFileFormat& ParsedDicomFile::GetDcmtkObject() const + { + if (pimpl_->file_.get() == NULL) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls, + "ReleaseDcmtkObject() was called"); + } + else + { + return *pimpl_->file_; + } + } + + + DcmFileFormat* ParsedDicomFile::ReleaseDcmtkObject() + { + if (pimpl_->file_.get() == NULL) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls, + "ReleaseDcmtkObject() was called"); + } + else + { + pimpl_->frameIndex_.reset(NULL); + return pimpl_->file_.release(); + } + } + + + ParsedDicomFile* ParsedDicomFile::Clone(bool keepSopInstanceUid) + { + return new ParsedDicomFile(*this, keepSopInstanceUid); + } + + + bool ParsedDicomFile::EmbedContentInternal(const std::string& dataUriScheme) + { + std::string mimeString, content; + if (!Toolbox::DecodeDataUriScheme(mimeString, content, dataUriScheme)) + { + return false; + } + + Toolbox::ToLowerCase(mimeString); + MimeType mime = StringToMimeType(mimeString); + + switch (mime) + { + case MimeType_Png: +#if ORTHANC_ENABLE_PNG == 1 + EmbedImage(mime, content); + break; +#else + throw OrthancException(ErrorCode_NotImplemented, + "Orthanc was compiled without support of PNG"); +#endif + + case MimeType_Jpeg: +#if ORTHANC_ENABLE_JPEG == 1 + EmbedImage(mime, content); + break; +#else + throw OrthancException(ErrorCode_NotImplemented, + "Orthanc was compiled without support of JPEG"); +#endif + + case MimeType_Pam: + EmbedImage(mime, content); + break; + + case MimeType_Pdf: + EmbedPdf(content); + break; + + default: + throw OrthancException(ErrorCode_NotImplemented, + "Unsupported MIME type for the content of a new DICOM file: " + + std::string(EnumerationToString(mime))); + } + + return true; + } + + + void ParsedDicomFile::EmbedContent(const std::string& dataUriScheme) + { + if (!EmbedContentInternal(dataUriScheme)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + void ParsedDicomFile::EmbedImage(MimeType mime, + const std::string& content) + { + switch (mime) + { + +#if ORTHANC_ENABLE_JPEG == 1 + case MimeType_Jpeg: + { + JpegReader reader; + reader.ReadFromMemory(content); + EmbedImage(reader); + break; + } +#endif + +#if ORTHANC_ENABLE_PNG == 1 + case MimeType_Png: + { + PngReader reader; + reader.ReadFromMemory(content); + EmbedImage(reader); + break; + } +#endif + + case MimeType_Pam: + { + PamReader reader; + reader.ReadFromMemory(content); + EmbedImage(reader); + break; + } + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void ParsedDicomFile::EmbedImage(const ImageAccessor& accessor) + { + if (accessor.GetFormat() != PixelFormat_Grayscale8 && + accessor.GetFormat() != PixelFormat_Grayscale16 && + accessor.GetFormat() != PixelFormat_SignedGrayscale16 && + accessor.GetFormat() != PixelFormat_RGB24 && + accessor.GetFormat() != PixelFormat_RGBA32) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + InvalidateCache(); + + if (accessor.GetFormat() == PixelFormat_RGBA32) + { + LOG(WARNING) << "Getting rid of the alpha channel when embedding a RGBA image inside DICOM"; + } + + // http://dicomiseasy.blogspot.be/2012/08/chapter-12-pixel-data.html + + Remove(DICOM_TAG_PIXEL_DATA); + ReplacePlainString(DICOM_TAG_COLUMNS, boost::lexical_cast<std::string>(accessor.GetWidth())); + ReplacePlainString(DICOM_TAG_ROWS, boost::lexical_cast<std::string>(accessor.GetHeight())); + ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "1"); + + // The "Number of frames" must only be present in multi-frame images + //ReplacePlainString(DICOM_TAG_NUMBER_OF_FRAMES, "1"); + + if (accessor.GetFormat() == PixelFormat_SignedGrayscale16) + { + ReplacePlainString(DICOM_TAG_PIXEL_REPRESENTATION, "1"); + } + else + { + ReplacePlainString(DICOM_TAG_PIXEL_REPRESENTATION, "0"); // Unsigned pixels + } + + unsigned int bytesPerPixel = 0; + + switch (accessor.GetFormat()) + { + case PixelFormat_Grayscale8: + // By default, grayscale images are MONOCHROME2 + SetIfAbsent(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "MONOCHROME2"); + + ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "8"); + ReplacePlainString(DICOM_TAG_BITS_STORED, "8"); + ReplacePlainString(DICOM_TAG_HIGH_BIT, "7"); + bytesPerPixel = 1; + break; + + case PixelFormat_RGB24: + case PixelFormat_RGBA32: + ReplacePlainString(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "RGB"); + ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "3"); + ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "8"); + ReplacePlainString(DICOM_TAG_BITS_STORED, "8"); + ReplacePlainString(DICOM_TAG_HIGH_BIT, "7"); + bytesPerPixel = 3; + + // "Planar configuration" must only present if "Samples per + // Pixel" is greater than 1 + ReplacePlainString(DICOM_TAG_PLANAR_CONFIGURATION, "0"); // Color channels are interleaved + + break; + + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + // By default, grayscale images are MONOCHROME2 + SetIfAbsent(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "MONOCHROME2"); + + ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "16"); + ReplacePlainString(DICOM_TAG_BITS_STORED, "16"); + ReplacePlainString(DICOM_TAG_HIGH_BIT, "15"); + bytesPerPixel = 2; + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + + assert(bytesPerPixel != 0); + + DcmTag key(DICOM_TAG_PIXEL_DATA.GetGroup(), + DICOM_TAG_PIXEL_DATA.GetElement()); + + std::unique_ptr<DcmPixelData> pixels(new DcmPixelData(key)); + + unsigned int pitch = accessor.GetWidth() * bytesPerPixel; + Uint8* target = NULL; + pixels->createUint8Array(accessor.GetHeight() * pitch, target); + + for (unsigned int y = 0; y < accessor.GetHeight(); y++) + { + switch (accessor.GetFormat()) + { + case PixelFormat_RGB24: + case PixelFormat_Grayscale8: + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + { + memcpy(target, reinterpret_cast<const Uint8*>(accessor.GetConstRow(y)), pitch); + target += pitch; + break; + } + + case PixelFormat_RGBA32: + { + // The alpha channel is not supported by the DICOM standard + const Uint8* source = reinterpret_cast<const Uint8*>(accessor.GetConstRow(y)); + for (unsigned int x = 0; x < accessor.GetWidth(); x++, target += 3, source += 4) + { + target[0] = source[0]; + target[1] = source[1]; + target[2] = source[2]; + } + + break; + } + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + if (!GetDcmtkObject().getDataset()->insert(pixels.release(), false, false).good()) + { + throw OrthancException(ErrorCode_InternalError); + } + } + + + Encoding ParsedDicomFile::DetectEncoding(bool& hasCodeExtensions) const + { + return FromDcmtkBridge::DetectEncoding(hasCodeExtensions, + *GetDcmtkObject().getDataset(), + GetDefaultDicomEncoding()); + } + + + void ParsedDicomFile::SetEncoding(Encoding encoding) + { + if (encoding == Encoding_Windows1251) + { + // This Cyrillic codepage is not officially supported by the + // DICOM standard. Do not set the SpecificCharacterSet tag. + return; + } + + std::string s = GetDicomSpecificCharacterSet(encoding); + ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, s); + } + + void ParsedDicomFile::DatasetToJson(Json::Value& target, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength) + { + std::set<DicomTag> ignoreTagLength; + FromDcmtkBridge::ExtractDicomAsJson(target, *GetDcmtkObject().getDataset(), + format, flags, maxStringLength, + GetDefaultDicomEncoding(), ignoreTagLength); + } + + + void ParsedDicomFile::DatasetToJson(Json::Value& target, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength, + const std::set<DicomTag>& ignoreTagLength) + { + FromDcmtkBridge::ExtractDicomAsJson(target, *GetDcmtkObject().getDataset(), + format, flags, maxStringLength, + GetDefaultDicomEncoding(), ignoreTagLength); + } + + + void ParsedDicomFile::DatasetToJson(Json::Value& target, + const std::set<DicomTag>& ignoreTagLength) + { + FromDcmtkBridge::ExtractDicomAsJson(target, *GetDcmtkObject().getDataset(), ignoreTagLength); + } + + + void ParsedDicomFile::DatasetToJson(Json::Value& target) + { + const std::set<DicomTag> ignoreTagLength; + FromDcmtkBridge::ExtractDicomAsJson(target, *GetDcmtkObject().getDataset(), ignoreTagLength); + } + + + void ParsedDicomFile::HeaderToJson(Json::Value& target, + DicomToJsonFormat format) + { + FromDcmtkBridge::ExtractHeaderAsJson(target, *GetDcmtkObject().getMetaInfo(), format, DicomToJsonFlags_None, 0); + } + + + bool ParsedDicomFile::HasTag(const DicomTag& tag) const + { + DcmTag key(tag.GetGroup(), tag.GetElement()); + return GetDcmtkObject().getDataset()->tagExists(key); + } + + + void ParsedDicomFile::EmbedPdf(const std::string& pdf) + { + if (pdf.size() < 5 || // (*) + strncmp("%PDF-", pdf.c_str(), 5) != 0) + { + throw OrthancException(ErrorCode_BadFileFormat, "Not a PDF file"); + } + + InvalidateCache(); + + ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, UID_EncapsulatedPDFStorage); + ReplacePlainString(FromDcmtkBridge::Convert(DCM_Modality), "OT"); + ReplacePlainString(FromDcmtkBridge::Convert(DCM_ConversionType), "WSD"); + ReplacePlainString(FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument), MIME_PDF); + //ReplacePlainString(FromDcmtkBridge::Convert(DCM_SeriesNumber), "1"); + + std::unique_ptr<DcmPolymorphOBOW> element(new DcmPolymorphOBOW(DCM_EncapsulatedDocument)); + + size_t s = pdf.size(); + if (s & 1) + { + // The size of the buffer must be even + s += 1; + } + + Uint8* bytes = NULL; + OFCondition result = element->createUint8Array(s, bytes); + if (!result.good() || bytes == NULL) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + // Blank pad byte (no access violation, as "pdf.size() >= 5" because of (*) ) + bytes[s - 1] = 0; + + memcpy(bytes, pdf.c_str(), pdf.size()); + + DcmPolymorphOBOW* obj = element.release(); + result = GetDcmtkObject().getDataset()->insert(obj); + + if (!result.good()) + { + delete obj; + throw OrthancException(ErrorCode_NotEnoughMemory); + } + } + + + bool ParsedDicomFile::ExtractPdf(std::string& pdf) + { + std::string sop, mime; + + if (!GetTagValue(sop, DICOM_TAG_SOP_CLASS_UID) || + !GetTagValue(mime, FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument)) || + sop != UID_EncapsulatedPDFStorage || + mime != MIME_PDF) + { + return false; + } + + if (!GetTagValue(pdf, DICOM_TAG_ENCAPSULATED_DOCUMENT)) + { + return false; + } + + // Strip the possible pad byte at the end of file, because the + // encapsulated documents must always have an even length. The PDF + // format expects files to end with %%EOF followed by CR/LF. If + // the last character of the file is not a CR or LF, we assume it + // is a pad byte and remove it. + if (pdf.size() > 0) + { + char last = *pdf.rbegin(); + + if (last != 10 && last != 13) + { + pdf.resize(pdf.size() - 1); + } + } + + return true; + } + + + ParsedDicomFile* ParsedDicomFile::CreateFromJson(const Json::Value& json, + DicomFromJsonFlags flags, + const std::string& privateCreator) + { + const bool generateIdentifiers = (flags & DicomFromJsonFlags_GenerateIdentifiers) ? true : false; + const bool decodeDataUriScheme = (flags & DicomFromJsonFlags_DecodeDataUriScheme) ? true : false; + + std::unique_ptr<ParsedDicomFile> result(new ParsedDicomFile(generateIdentifiers)); + result->SetEncoding(FromDcmtkBridge::ExtractEncoding(json, GetDefaultDicomEncoding())); + + const Json::Value::Members tags = json.getMemberNames(); + + for (size_t i = 0; i < tags.size(); i++) + { + DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]); + const Json::Value& value = json[tags[i]]; + + if (tag == DICOM_TAG_PIXEL_DATA || + tag == DICOM_TAG_ENCAPSULATED_DOCUMENT) + { + if (value.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadRequest); + } + else + { + result->EmbedContent(value.asString()); + } + } + else if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET) + { + result->Replace(tag, value, decodeDataUriScheme, DicomReplaceMode_InsertIfAbsent, privateCreator); + } + } + + return result.release(); + } + + + void ParsedDicomFile::GetRawFrame(std::string& target, + MimeType& mime, + unsigned int frameId) + { + if (pimpl_->frameIndex_.get() == NULL) + { + assert(pimpl_->file_ != NULL && + GetDcmtkObject().getDataset() != NULL); + pimpl_->frameIndex_.reset(new DicomFrameIndex(*GetDcmtkObject().getDataset())); + } + + pimpl_->frameIndex_->GetRawFrame(target, frameId); + + E_TransferSyntax transferSyntax = GetDcmtkObject().getDataset()->getCurrentXfer(); + switch (transferSyntax) + { + case EXS_JPEGProcess1: + mime = MimeType_Jpeg; + break; + + case EXS_JPEG2000LosslessOnly: + case EXS_JPEG2000: + mime = MimeType_Jpeg2000; + break; + + default: + mime = MimeType_Binary; + break; + } + } + + + void ParsedDicomFile::InvalidateCache() + { + pimpl_->frameIndex_.reset(NULL); + } + + + unsigned int ParsedDicomFile::GetFramesCount() const + { + assert(pimpl_->file_ != NULL && + GetDcmtkObject().getDataset() != NULL); + return DicomFrameIndex::GetFramesCount(*GetDcmtkObject().getDataset()); + } + + + void ParsedDicomFile::ChangeEncoding(Encoding target) + { + bool hasCodeExtensions; + Encoding source = DetectEncoding(hasCodeExtensions); + + if (source != target) // Avoid unnecessary conversion + { + ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, GetDicomSpecificCharacterSet(target)); + FromDcmtkBridge::ChangeStringEncoding(*GetDcmtkObject().getDataset(), source, hasCodeExtensions, target); + } + } + + + void ParsedDicomFile::ExtractDicomSummary(DicomMap& target) const + { + FromDcmtkBridge::ExtractDicomSummary(target, *GetDcmtkObject().getDataset()); + } + + + void ParsedDicomFile::ExtractDicomSummary(DicomMap& target, + const std::set<DicomTag>& ignoreTagLength) const + { + FromDcmtkBridge::ExtractDicomSummary(target, *GetDcmtkObject().getDataset(), ignoreTagLength); + } + + + bool ParsedDicomFile::LookupTransferSyntax(std::string& result) + { +#if 0 + // This was the implementation in Orthanc <= 1.6.1 + + // TODO - Shouldn't "dataset.getCurrentXfer()" be used instead of + // using the meta header? + const char* value = NULL; + + if (GetDcmtkObject().getMetaInfo() != NULL && + GetDcmtkObject().getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() && + value != NULL) + { + result.assign(value); + return true; + } + else + { + return false; + } +#else + DicomTransferSyntax s; + if (FromDcmtkBridge::LookupOrthancTransferSyntax(s, GetDcmtkObject())) + { + result.assign(GetTransferSyntaxUid(s)); + return true; + } + else + { + return false; + } +#endif + } + + + bool ParsedDicomFile::LookupPhotometricInterpretation(PhotometricInterpretation& result) const + { + DcmTagKey k(DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetGroup(), + DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetElement()); + + DcmDataset& dataset = *GetDcmtkObject().getDataset(); + + const char *c = NULL; + if (dataset.findAndGetString(k, c).good() && + c != NULL) + { + result = StringToPhotometricInterpretation(c); + return true; + } + else + { + return false; + } + } + + + void ParsedDicomFile::Apply(ITagVisitor& visitor) + { + FromDcmtkBridge::Apply(*GetDcmtkObject().getDataset(), visitor, GetDefaultDicomEncoding()); + } +}