Mercurial > hg > orthanc
diff OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.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/FromDcmtkBridge.cpp@058b5ade8acd |
children | c02a2d9efbc2 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Wed Jun 10 20:30:34 2020 +0200 @@ -0,0 +1,2676 @@ +/** + * 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/>. + **/ + + +#include "../PrecompiledHeaders.h" + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#if !defined(ORTHANC_SANDBOXED) +# error The macro ORTHANC_SANDBOXED must be defined +#endif + +#if !defined(DCMTK_VERSION_NUMBER) +# error The macro DCMTK_VERSION_NUMBER must be defined +#endif + +#include "FromDcmtkBridge.h" +#include "ToDcmtkBridge.h" +#include "../Compatibility.h" +#include "../Logging.h" +#include "../Toolbox.h" +#include "../OrthancException.h" + +#if ORTHANC_SANDBOXED == 0 +# include "../TemporaryFile.h" +#endif + +#include <list> +#include <limits> + +#include <boost/lexical_cast.hpp> +#include <boost/filesystem.hpp> +#include <boost/algorithm/string/predicate.hpp> +#include <boost/algorithm/string/join.hpp> + +#include <dcmtk/dcmdata/dcdeftag.h> +#include <dcmtk/dcmdata/dcdicent.h> +#include <dcmtk/dcmdata/dcdict.h> +#include <dcmtk/dcmdata/dcfilefo.h> +#include <dcmtk/dcmdata/dcistrmb.h> +#include <dcmtk/dcmdata/dcostrmb.h> +#include <dcmtk/dcmdata/dcpixel.h> +#include <dcmtk/dcmdata/dcuid.h> +#include <dcmtk/dcmdata/dcxfer.h> + +#include <dcmtk/dcmdata/dcvrae.h> +#include <dcmtk/dcmdata/dcvras.h> +#include <dcmtk/dcmdata/dcvrat.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> + +#if DCMTK_VERSION_NUMBER >= 361 +# include <dcmtk/dcmdata/dcvruc.h> +# include <dcmtk/dcmdata/dcvrur.h> +#endif + +#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 +# include <OrthancFrameworkResources.h> +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG == 1 +# include <dcmtk/dcmjpeg/djdecode.h> +# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 +# include <dcmtk/dcmjpeg/djencode.h> +# endif +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 +# include <dcmtk/dcmjpls/djdecode.h> +# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 +# include <dcmtk/dcmjpls/djencode.h> +# endif +#endif + + +#include <dcmtk/dcmdata/dcrledrg.h> +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 +# include <dcmtk/dcmdata/dcrleerg.h> +# include <dcmtk/dcmimage/diregist.h> // include to support color images +#endif + + +namespace Orthanc +{ + static bool IsBinaryTag(const DcmTag& key) + { + return (key.isUnknownVR() || + key.getEVR() == EVR_OB || + key.getEVR() == EVR_OW || + key.getEVR() == EVR_UN || + key.getEVR() == EVR_ox); + } + + +#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 + static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary, + FrameworkResources::FileResourceId resource) + { + std::string content; + FrameworkResources::GetFileResource(content, resource); + +#if ORTHANC_SANDBOXED == 0 + TemporaryFile tmp; + tmp.Write(content); + + if (!dictionary.loadDictionary(tmp.GetPath().c_str())) + { + throw OrthancException(ErrorCode_InternalError, + "Cannot read embedded dictionary. Under Windows, make sure that " + "your TEMP directory does not contain special characters."); + } +#else + if (!dictionary.loadFromMemory(content)) + { + throw OrthancException(ErrorCode_InternalError, + "Cannot read embedded dictionary. Under Windows, make sure that " + "your TEMP directory does not contain special characters."); + } +#endif + } +#endif + + + namespace + { + class DictionaryLocker : public boost::noncopyable + { + private: + DcmDataDictionary& dictionary_; + + public: + DictionaryLocker() : dictionary_(dcmDataDict.wrlock()) + { + } + + ~DictionaryLocker() + { +#if DCMTK_VERSION_NUMBER >= 364 + dcmDataDict.wrunlock(); +#else + dcmDataDict.unlock(); +#endif + } + + DcmDataDictionary& operator*() + { + return dictionary_; + } + + DcmDataDictionary* operator->() + { + return &dictionary_; + } + }; + + +#define DCMTK_TO_CTYPE_CONVERTER(converter, cType, dcmtkType, getter) \ + \ + struct converter \ + { \ + typedef cType CType; \ + \ + static bool Apply(CType& result, \ + DcmElement& element, \ + size_t i) \ + { \ + return dynamic_cast<dcmtkType&>(element).getter(result, i).good(); \ + } \ + }; + +DCMTK_TO_CTYPE_CONVERTER(DcmtkToSint32Converter, Sint32, DcmSignedLong, getSint32) +DCMTK_TO_CTYPE_CONVERTER(DcmtkToSint16Converter, Sint16, DcmSignedShort, getSint16) +DCMTK_TO_CTYPE_CONVERTER(DcmtkToUint32Converter, Uint32, DcmUnsignedLong, getUint32) +DCMTK_TO_CTYPE_CONVERTER(DcmtkToUint16Converter, Uint16, DcmUnsignedShort, getUint16) +DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat32Converter, Float32, DcmFloatingPointSingle, getFloat32) +DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDouble, getFloat64) + + + template <typename F> + static DicomValue* ApplyDcmtkToCTypeConverter(DcmElement& element) + { + F f; + typename F::CType value; + + if (element.getLength() > sizeof(typename F::CType) + && (element.getLength() % sizeof(typename F::CType)) == 0) + { + size_t count = element.getLength() / sizeof(typename F::CType); + std::vector<std::string> strings; + for (size_t i = 0; i < count; i++) { + if (f.Apply(value, element, i)) { + strings.push_back(boost::lexical_cast<std::string>(value)); + } + } + return new DicomValue(boost::algorithm::join(strings, "\\"), false); + } + else if (f.Apply(value, element, 0)) { + return new DicomValue(boost::lexical_cast<std::string>(value), false); + } + else { + return new DicomValue; + } + } + + } + + + void FromDcmtkBridge::InitializeDictionary(bool loadPrivateDictionary) + { + LOG(INFO) << "Using DCTMK version: " << DCMTK_VERSION_NUMBER; + + { + DictionaryLocker locker; + + locker->clear(); + +#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 + LOG(INFO) << "Loading the embedded dictionaries"; + /** + * Do not load DICONDE dictionary, it breaks the other tags. The + * command "strace storescu 2>&1 |grep dic" shows that DICONDE + * dictionary is not loaded by storescu. + **/ + //LoadEmbeddedDictionary(*locker, FrameworkResources::DICTIONARY_DICONDE); + + LoadEmbeddedDictionary(*locker, FrameworkResources::DICTIONARY_DICOM); + + if (loadPrivateDictionary) + { + LOG(INFO) << "Loading the embedded dictionary of private tags"; + LoadEmbeddedDictionary(*locker, FrameworkResources::DICTIONARY_PRIVATE); + } + else + { + LOG(INFO) << "The dictionary of private tags has not been loaded"; + } + +#else + std::vector<std::string> dictionaries; + + const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE); + if (env != NULL) + { + // This mimics the behavior of DCMTK: + // https://support.dcmtk.org/docs/file_envvars.html +#if defined(_WIN32) + Toolbox::TokenizeString(dictionaries, std::string(env), ';'); +#else + Toolbox::TokenizeString(dictionaries, std::string(env), ':'); +#endif + } + else + { + boost::filesystem::path base = DCMTK_DICTIONARY_DIR; + dictionaries.push_back((base / "dicom.dic").string()); + dictionaries.push_back((base / "private.dic").string()); + } + + for (size_t i = 0; i < dictionaries.size(); i++) + { + LOG(WARNING) << "Loading external DICOM dictionary: \"" << dictionaries[i] << "\""; + + if (!locker->loadDictionary(dictionaries[i].c_str())) + { + throw OrthancException(ErrorCode_InexistentFile); + } + } + +#endif + } + + /* make sure data dictionary is loaded */ + if (!dcmDataDict.isDictionaryLoaded()) + { + throw OrthancException(ErrorCode_InternalError, + "No DICOM dictionary loaded, check environment variable: " + + std::string(DCM_DICT_ENVIRONMENT_VARIABLE)); + } + + { + // Test the dictionary with a simple DICOM tag + DcmTag key(0x0010, 0x1030); // This is PatientWeight + if (key.getEVR() != EVR_DS) + { + throw OrthancException(ErrorCode_InternalError, + "The DICOM dictionary has not been correctly read"); + } + } + } + + + void FromDcmtkBridge::RegisterDictionaryTag(const DicomTag& tag, + ValueRepresentation vr, + const std::string& name, + unsigned int minMultiplicity, + unsigned int maxMultiplicity, + const std::string& privateCreator) + { + if (minMultiplicity < 1) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + bool arbitrary = false; + if (maxMultiplicity == 0) + { + maxMultiplicity = DcmVariableVM; + arbitrary = true; + } + else if (maxMultiplicity < minMultiplicity) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + DcmEVR evr = ToDcmtkBridge::Convert(vr); + + LOG(INFO) << "Registering tag in dictionary: " << tag << " " << (DcmVR(evr).getValidVRName()) << " " + << name << " (multiplicity: " << minMultiplicity << "-" + << (arbitrary ? "n" : boost::lexical_cast<std::string>(maxMultiplicity)) << ")"; + + std::unique_ptr<DcmDictEntry> entry; + if (privateCreator.empty()) + { + if (tag.GetGroup() % 2 == 1) + { + char buf[128]; + sprintf(buf, "Warning: You are registering a private tag (%04x,%04x), " + "but no private creator was associated with it", + tag.GetGroup(), tag.GetElement()); + LOG(WARNING) << buf; + } + + entry.reset(new DcmDictEntry(tag.GetGroup(), + tag.GetElement(), + evr, name.c_str(), + static_cast<int>(minMultiplicity), + static_cast<int>(maxMultiplicity), + NULL /* version */, + OFTrue /* doCopyString */, + NULL /* private creator */)); + } + else + { + // "Private Data Elements have an odd Group Number that is not + // (0001,eeee), (0003,eeee), (0005,eeee), (0007,eeee), or + // (FFFF,eeee)." + if (tag.GetGroup() % 2 == 0 /* even */ || + tag.GetGroup() == 0x0001 || + tag.GetGroup() == 0x0003 || + tag.GetGroup() == 0x0005 || + tag.GetGroup() == 0x0007 || + tag.GetGroup() == 0xffff) + { + char buf[128]; + sprintf(buf, "Trying to register private tag (%04x,%04x), but it must have an odd group >= 0x0009", + tag.GetGroup(), tag.GetElement()); + throw OrthancException(ErrorCode_ParameterOutOfRange, std::string(buf)); + } + + entry.reset(new DcmDictEntry(tag.GetGroup(), + tag.GetElement(), + evr, name.c_str(), + static_cast<int>(minMultiplicity), + static_cast<int>(maxMultiplicity), + "private" /* version */, + OFTrue /* doCopyString */, + privateCreator.c_str())); + } + + entry->setGroupRangeRestriction(DcmDictRange_Unspecified); + entry->setElementRangeRestriction(DcmDictRange_Unspecified); + + { + DictionaryLocker locker; + + if (locker->findEntry(name.c_str())) + { + throw OrthancException(ErrorCode_AlreadyExistingTag, + "Cannot register two tags with the same symbolic name \"" + name + "\""); + } + + locker->addEntry(entry.release()); + } + } + + + Encoding FromDcmtkBridge::DetectEncoding(bool& hasCodeExtensions, + DcmItem& dataset, + Encoding defaultEncoding) + { + // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.12.html#sect_C.12.1.1.2 + + OFString tmp; + if (dataset.findAndGetOFStringArray(DCM_SpecificCharacterSet, tmp).good()) + { + std::vector<std::string> tokens; + Toolbox::TokenizeString(tokens, std::string(tmp.c_str()), '\\'); + + hasCodeExtensions = (tokens.size() > 1); + + for (size_t i = 0; i < tokens.size(); i++) + { + std::string characterSet = Toolbox::StripSpaces(tokens[i]); + + if (!characterSet.empty()) + { + Encoding encoding; + + if (GetDicomEncoding(encoding, characterSet.c_str())) + { + // The specific character set is supported by the Orthanc core + return encoding; + } + else + { + LOG(WARNING) << "Value of Specific Character Set (0008,0005) is not supported: " << characterSet + << ", fallback to ASCII (remove all special characters)"; + return Encoding_Ascii; + } + } + } + } + else + { + hasCodeExtensions = false; + } + + // No specific character set tag: Use the default encoding + return defaultEncoding; + } + + + void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target, + DcmItem& dataset, + unsigned int maxStringLength, + Encoding defaultEncoding, + const std::set<DicomTag>& ignoreTagLength) + { + bool hasCodeExtensions; + Encoding encoding = DetectEncoding(hasCodeExtensions, dataset, defaultEncoding); + + target.Clear(); + for (unsigned long i = 0; i < dataset.card(); i++) + { + DcmElement* element = dataset.getElement(i); + if (element && element->isLeaf()) + { + target.SetValueInternal(element->getTag().getGTag(), + element->getTag().getETag(), + ConvertLeafElement(*element, DicomToJsonFlags_Default, + maxStringLength, encoding, hasCodeExtensions, ignoreTagLength)); + } + } + } + + + DicomTag FromDcmtkBridge::Convert(const DcmTag& tag) + { + return DicomTag(tag.getGTag(), tag.getETag()); + } + + + DicomTag FromDcmtkBridge::GetTag(const DcmElement& element) + { + return DicomTag(element.getGTag(), element.getETag()); + } + + + DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element, + DicomToJsonFlags flags, + unsigned int maxStringLength, + Encoding encoding, + bool hasCodeExtensions, + const std::set<DicomTag>& ignoreTagLength) + { + if (!element.isLeaf()) + { + // This function is only applicable to leaf elements + throw OrthancException(ErrorCode_BadParameterType); + } + + char *c = NULL; + if (element.isaString() && + element.getString(c).good()) + { + if (c == NULL) // This case corresponds to the empty string + { + return new DicomValue("", false); + } + else + { + std::string s(c); + std::string utf8 = Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions); + + if (maxStringLength != 0 && + utf8.size() > maxStringLength && + ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end()) + { + return new DicomValue; // Too long, create a NULL value + } + else + { + return new DicomValue(utf8, false); + } + } + } + + + if (element.getVR() == EVR_UN) + { + // Unknown value representation: Lookup in the dictionary. This + // is notably the case for private tags registered with the + // "Dictionary" configuration option. + DictionaryLocker locker; + + const DcmDictEntry* entry = locker->findEntry(element.getTag().getXTag(), + element.getTag().getPrivateCreator()); + if (entry != NULL && + entry->getVR().isaString()) + { + Uint8* data = NULL; + + // At (*), we do not try and convert to UTF-8, as nothing says + // the encoding of the private tag is the same as that of the + // remaining of the DICOM dataset. Only go for ASCII strings. + + if (element.getUint8Array(data) == EC_Normal && + Toolbox::IsAsciiString(data, element.getLength())) // (*) + { + if (data == NULL) + { + return new DicomValue("", false); // Empty string + } + else if (maxStringLength != 0 && + element.getLength() > maxStringLength && + ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end()) + { + return new DicomValue; // Too long, create a NULL value + } + else + { + std::string s(reinterpret_cast<const char*>(data), element.getLength()); + return new DicomValue(s, false); + } + } + } + } + + + try + { + // http://support.dcmtk.org/docs/dcvr_8h-source.html + switch (element.getVR()) + { + + /** + * Deal with binary data (including PixelData). + **/ + + case EVR_OB: // other byte + case EVR_OF: // other float + case EVR_OW: // other word + case EVR_UN: // unknown value representation + case EVR_ox: // OB or OW depending on context + case EVR_DS: // decimal string + case EVR_IS: // integer string + case EVR_AS: // age string + case EVR_DA: // date string + case EVR_DT: // date time string + case EVR_TM: // time string + case EVR_AE: // application entity title + case EVR_CS: // code string + case EVR_SH: // short string + case EVR_LO: // long string + case EVR_ST: // short text + case EVR_LT: // long text + case EVR_UT: // unlimited text + case EVR_PN: // person name + case EVR_UI: // unique identifier + case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) + case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR + { + if (!(flags & DicomToJsonFlags_ConvertBinaryToNull)) + { + Uint8* data = NULL; + if (element.getUint8Array(data) == EC_Normal) + { + return new DicomValue(reinterpret_cast<const char*>(data), element.getLength(), true); + } + } + + return new DicomValue; + } + + /** + * Numeric types + **/ + + case EVR_SL: // signed long + { + return ApplyDcmtkToCTypeConverter<DcmtkToSint32Converter>(element); + } + + case EVR_SS: // signed short + { + return ApplyDcmtkToCTypeConverter<DcmtkToSint16Converter>(element); + } + + case EVR_UL: // unsigned long + { + return ApplyDcmtkToCTypeConverter<DcmtkToUint32Converter>(element); + } + + case EVR_US: // unsigned short + { + return ApplyDcmtkToCTypeConverter<DcmtkToUint16Converter>(element); + } + + case EVR_FL: // float single-precision + { + return ApplyDcmtkToCTypeConverter<DcmtkToFloat32Converter>(element); + } + + case EVR_FD: // float double-precision + { + return ApplyDcmtkToCTypeConverter<DcmtkToFloat64Converter>(element); + } + + + /** + * Attribute tag. + **/ + + case EVR_AT: + { + DcmTagKey tag; + if (dynamic_cast<DcmAttributeTag&>(element).getTagVal(tag, 0).good()) + { + DicomTag t(tag.getGroup(), tag.getElement()); + return new DicomValue(t.Format(), false); + } + else + { + return new DicomValue; + } + } + + + /** + * Sequence types, should never occur at this point because of + * "element.isLeaf()". + **/ + + case EVR_SQ: // sequence of items + return new DicomValue; + + + /** + * Internal to DCMTK. + **/ + + case EVR_xs: // SS or US depending on context + case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) + case EVR_na: // na="not applicable", for data which has no VR + case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor + case EVR_item: // used internally for items + case EVR_metainfo: // used internally for meta info datasets + case EVR_dataset: // used internally for datasets + case EVR_fileFormat: // used internally for DICOM files + case EVR_dicomDir: // used internally for DICOMDIR objects + case EVR_dirRecord: // used internally for DICOMDIR records + case EVR_pixelSQ: // used internally for pixel sequences in a compressed image + case EVR_pixelItem: // used internally for pixel items in a compressed image + case EVR_PixelData: // used internally for uncompressed pixeld data + case EVR_OverlayData: // used internally for overlay data + return new DicomValue; + + + /** + * Default case. + **/ + + default: + return new DicomValue; + } + } + catch (boost::bad_lexical_cast&) + { + return new DicomValue; + } + catch (std::bad_cast&) + { + return new DicomValue; + } + } + + + static Json::Value& PrepareNode(Json::Value& parent, + DcmElement& element, + DicomToJsonFormat format) + { + assert(parent.type() == Json::objectValue); + + DicomTag tag(FromDcmtkBridge::GetTag(element)); + const std::string formattedTag = tag.Format(); + + if (format == DicomToJsonFormat_Short) + { + parent[formattedTag] = Json::nullValue; + return parent[formattedTag]; + } + + // This code gives access to the name of the private tags + std::string tagName = FromDcmtkBridge::GetTagName(element); + + switch (format) + { + case DicomToJsonFormat_Human: + parent[tagName] = Json::nullValue; + return parent[tagName]; + + case DicomToJsonFormat_Full: + { + parent[formattedTag] = Json::objectValue; + Json::Value& node = parent[formattedTag]; + + if (element.isLeaf()) + { + node["Name"] = tagName; + + if (element.getTag().getPrivateCreator() != NULL) + { + node["PrivateCreator"] = element.getTag().getPrivateCreator(); + } + + return node; + } + else + { + node["Name"] = tagName; + node["Type"] = "Sequence"; + node["Value"] = Json::nullValue; + return node["Value"]; + } + } + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + static void LeafValueToJson(Json::Value& target, + const DicomValue& value, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength) + { + Json::Value* targetValue = NULL; + Json::Value* targetType = NULL; + + switch (format) + { + case DicomToJsonFormat_Short: + case DicomToJsonFormat_Human: + { + assert(target.type() == Json::nullValue); + targetValue = ⌖ + break; + } + + case DicomToJsonFormat_Full: + { + assert(target.type() == Json::objectValue); + target["Value"] = Json::nullValue; + target["Type"] = Json::nullValue; + targetType = &target["Type"]; + targetValue = &target["Value"]; + break; + } + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + assert(targetValue != NULL); + assert(targetValue->type() == Json::nullValue); + assert(targetType == NULL || targetType->type() == Json::nullValue); + + if (value.IsNull()) + { + if (targetType != NULL) + { + *targetType = "Null"; + } + } + else if (value.IsBinary()) + { + if (flags & DicomToJsonFlags_ConvertBinaryToAscii) + { + *targetValue = Toolbox::ConvertToAscii(value.GetContent()); + } + else + { + std::string s; + value.FormatDataUriScheme(s); + *targetValue = s; + } + + if (targetType != NULL) + { + *targetType = "Binary"; + } + } + else if (maxStringLength == 0 || + value.GetContent().size() <= maxStringLength) + { + *targetValue = value.GetContent(); + + if (targetType != NULL) + { + *targetType = "String"; + } + } + else + { + if (targetType != NULL) + { + *targetType = "TooLong"; + } + } + } + + + void FromDcmtkBridge::ElementToJson(Json::Value& parent, + DcmElement& element, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength, + Encoding encoding, + bool hasCodeExtensions, + const std::set<DicomTag>& ignoreTagLength) + { + if (parent.type() == Json::nullValue) + { + parent = Json::objectValue; + } + + assert(parent.type() == Json::objectValue); + Json::Value& target = PrepareNode(parent, element, format); + + if (element.isLeaf()) + { + // The "0" below lets "LeafValueToJson()" take care of "TooLong" values + std::unique_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement + (element, flags, 0, encoding, hasCodeExtensions, ignoreTagLength)); + + if (ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end()) + { + LeafValueToJson(target, *v, format, flags, maxStringLength); + } + else + { + LeafValueToJson(target, *v, format, flags, 0); + } + } + else + { + assert(target.type() == Json::nullValue); + target = Json::arrayValue; + + // "All subclasses of DcmElement except for DcmSequenceOfItems + // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset + // etc. are not." The following dynamic_cast is thus OK. + DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(element); + + for (unsigned long i = 0; i < sequence.card(); i++) + { + DcmItem* child = sequence.getItem(i); + Json::Value& v = target.append(Json::objectValue); + DatasetToJson(v, *child, format, flags, maxStringLength, encoding, hasCodeExtensions, ignoreTagLength); + } + } + } + + + void FromDcmtkBridge::DatasetToJson(Json::Value& parent, + DcmItem& item, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength, + Encoding encoding, + bool hasCodeExtensions, + const std::set<DicomTag>& ignoreTagLength) + { + assert(parent.type() == Json::objectValue); + + for (unsigned long i = 0; i < item.card(); i++) + { + DcmElement* element = item.getElement(i); + if (element == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + DicomTag tag(FromDcmtkBridge::Convert(element->getTag())); + + /*element->getTag().isPrivate()*/ + if (tag.IsPrivate() && + !(flags & DicomToJsonFlags_IncludePrivateTags)) + { + continue; + } + + if (!(flags & DicomToJsonFlags_IncludeUnknownTags)) + { + DictionaryLocker locker; + if (locker->findEntry(element->getTag(), element->getTag().getPrivateCreator()) == NULL) + { + continue; + } + } + + if (IsBinaryTag(element->getTag())) + { + // This is a binary tag + if ((tag == DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludePixelData)) || + (tag != DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludeBinary))) + { + continue; + } + } + + FromDcmtkBridge::ElementToJson(parent, *element, format, flags, + maxStringLength, encoding, hasCodeExtensions, ignoreTagLength); + } + } + + + void FromDcmtkBridge::ExtractDicomAsJson(Json::Value& target, + DcmDataset& dataset, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength, + Encoding defaultEncoding, + const std::set<DicomTag>& ignoreTagLength) + { + bool hasCodeExtensions; + Encoding encoding = DetectEncoding(hasCodeExtensions, dataset, defaultEncoding); + + target = Json::objectValue; + DatasetToJson(target, dataset, format, flags, maxStringLength, encoding, hasCodeExtensions, ignoreTagLength); + } + + + void FromDcmtkBridge::ExtractHeaderAsJson(Json::Value& target, + DcmMetaInfo& dataset, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength) + { + std::set<DicomTag> ignoreTagLength; + target = Json::objectValue; + DatasetToJson(target, dataset, format, flags, maxStringLength, Encoding_Ascii, false, ignoreTagLength); + } + + + + static std::string GetTagNameInternal(DcmTag& tag) + { + { + // Some patches for important tags because of different DICOM + // dictionaries between DCMTK versions + DicomTag tmp(tag.getGroup(), tag.getElement()); + std::string n = tmp.GetMainTagsName(); + if (n.size() != 0) + { + return n; + } + // End of patches + } + +#if 0 + // This version explicitly calls the dictionary + const DcmDataDictionary& dict = dcmDataDict.rdlock(); + const DcmDictEntry* entry = dict.findEntry(tag, NULL); + + std::string s(DcmTag_ERROR_TagName); + if (entry != NULL) + { + s = std::string(entry->getTagName()); + } + + dcmDataDict.unlock(); + return s; +#else + const char* name = tag.getTagName(); + if (name == NULL) + { + return DcmTag_ERROR_TagName; + } + else + { + return std::string(name); + } +#endif + } + + + std::string FromDcmtkBridge::GetTagName(const DicomTag& t, + const std::string& privateCreator) + { + DcmTag tag(t.GetGroup(), t.GetElement()); + + if (!privateCreator.empty()) + { + tag.setPrivateCreator(privateCreator.c_str()); + } + + return GetTagNameInternal(tag); + } + + + std::string FromDcmtkBridge::GetTagName(const DcmElement& element) + { + // Copy the tag to ensure const-correctness of DcmElement. Note + // that the private creator information is also copied. + DcmTag tag(element.getTag()); + + return GetTagNameInternal(tag); + } + + + + DicomTag FromDcmtkBridge::ParseTag(const char* name) + { + DicomTag parsed(0, 0); + if (DicomTag::ParseHexadecimal(parsed, name)) + { + return parsed; + } + +#if 0 + const DcmDataDictionary& dict = dcmDataDict.rdlock(); + const DcmDictEntry* entry = dict.findEntry(name); + + if (entry == NULL) + { + dcmDataDict.unlock(); + throw OrthancException(ErrorCode_UnknownDicomTag); + } + else + { + DcmTagKey key = entry->getKey(); + DicomTag tag(key.getGroup(), key.getElement()); + dcmDataDict.unlock(); + return tag; + } +#else + DcmTag tag; + if (DcmTag::findTagFromName(name, tag).good()) + { + return DicomTag(tag.getGTag(), tag.getETag()); + } + else + { + LOG(INFO) << "Unknown DICOM tag: \"" << name << "\""; + throw OrthancException(ErrorCode_UnknownDicomTag); + } +#endif + } + + + bool FromDcmtkBridge::IsUnknownTag(const DicomTag& tag) + { + DcmTag tmp(tag.GetGroup(), tag.GetElement()); + return tmp.isUnknownVR(); + } + + + void FromDcmtkBridge::ToJson(Json::Value& result, + const DicomMap& values, + bool simplify) + { + if (result.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + result.clear(); + + for (DicomMap::Content::const_iterator + it = values.content_.begin(); it != values.content_.end(); ++it) + { + // TODO Inject PrivateCreator if some is available in the DicomMap? + const std::string tagName = GetTagName(it->first, ""); + + if (simplify) + { + if (it->second->IsNull()) + { + result[tagName] = Json::nullValue; + } + else + { + // TODO IsBinary + result[tagName] = it->second->GetContent(); + } + } + else + { + Json::Value value = Json::objectValue; + + value["Name"] = tagName; + + if (it->second->IsNull()) + { + value["Type"] = "Null"; + value["Value"] = Json::nullValue; + } + else + { + // TODO IsBinary + value["Type"] = "String"; + value["Value"] = it->second->GetContent(); + } + + result[it->first.Format()] = value; + } + } + } + + + std::string FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType level) + { + char uid[100]; + + switch (level) + { + case ResourceType_Patient: + // The "PatientID" field is of type LO (Long String), 64 + // Bytes Maximum. An UUID is of length 36, thus it can be used + // as a random PatientID. + return Toolbox::GenerateUuid(); + + case ResourceType_Instance: + return dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT); + + case ResourceType_Series: + return dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT); + + case ResourceType_Study: + return dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT); + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + + static bool SaveToMemoryBufferInternal(std::string& buffer, + DcmFileFormat& dicom, + E_TransferSyntax xfer) + { + E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength; + + // Create a memory buffer with the proper size + { + const uint32_t estimatedSize = dicom.calcElementLength(xfer, encodingType); // (*) + buffer.resize(estimatedSize); + } + + DcmOutputBufferStream ob(&buffer[0], buffer.size()); + + // Fill the memory buffer with the meta-header and the dataset + dicom.transferInit(); + OFCondition c = dicom.write(ob, xfer, encodingType, NULL, + /*opt_groupLength*/ EGL_recalcGL, + /*opt_paddingType*/ EPD_noChange, + /*padlen*/ 0, /*subPadlen*/ 0, /*instanceLength*/ 0, + EWM_updateMeta /* creates new SOP instance UID on lossy */); + dicom.transferEnd(); + + if (c.good()) + { + // The DICOM file is successfully written, truncate the target + // buffer if its size was overestimated by (*) + ob.flush(); + + size_t effectiveSize = static_cast<size_t>(ob.tell()); + if (effectiveSize < buffer.size()) + { + buffer.resize(effectiveSize); + } + + return true; + } + else + { + // Error + buffer.clear(); + return false; + } + } + + + bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer, + DcmDataset& dataSet) + { + // Determine the transfer syntax which shall be used to write the + // information to the file. If not possible, switch to the Little + // Endian syntax, with explicit length. + + // http://support.dcmtk.org/docs/dcxfer_8h-source.html + + + /** + * Note that up to Orthanc 0.7.1 (inclusive), the + * "EXS_LittleEndianExplicit" was always used to save the DICOM + * dataset into memory. We now keep the original transfer syntax + * (if available). + **/ + E_TransferSyntax xfer = dataSet.getCurrentXfer(); + if (xfer == EXS_Unknown) + { + // No information about the original transfer syntax: This is + // most probably a DICOM dataset that was read from memory. + xfer = EXS_LittleEndianExplicit; + } + + // Create the meta-header information + DcmFileFormat ff(&dataSet); + ff.validateMetaInfo(xfer); + ff.removeInvalidGroups(); + + return SaveToMemoryBufferInternal(buffer, ff, xfer); + } + + + bool FromDcmtkBridge::Transcode(DcmFileFormat& dicom, + DicomTransferSyntax syntax, + const DcmRepresentationParameter* representation) + { + E_TransferSyntax xfer; + if (!LookupDcmtkTransferSyntax(xfer, syntax)) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + DicomTransferSyntax sourceSyntax; + bool known = LookupOrthancTransferSyntax(sourceSyntax, dicom); + + if (!dicom.chooseRepresentation(xfer, representation).good() || + !dicom.canWriteXfer(xfer) || + !dicom.validateMetaInfo(xfer, EWM_updateMeta).good()) + { + return false; + } + else + { + dicom.removeInvalidGroups(); + + if (known) + { + LOG(INFO) << "Transcoded an image from transfer syntax " + << GetTransferSyntaxUid(sourceSyntax) << " to " + << GetTransferSyntaxUid(syntax); + } + else + { + LOG(INFO) << "Transcoded an image from unknown transfer syntax to " + << GetTransferSyntaxUid(syntax); + } + + return true; + } + } + } + + + ValueRepresentation FromDcmtkBridge::LookupValueRepresentation(const DicomTag& tag) + { + DcmTag t(tag.GetGroup(), tag.GetElement()); + return Convert(t.getEVR()); + } + + ValueRepresentation FromDcmtkBridge::Convert(const DcmEVR vr) + { + switch (vr) + { + case EVR_AE: + return ValueRepresentation_ApplicationEntity; + + case EVR_AS: + return ValueRepresentation_AgeString; + + case EVR_AT: + return ValueRepresentation_AttributeTag; + + case EVR_CS: + return ValueRepresentation_CodeString; + + case EVR_DA: + return ValueRepresentation_Date; + + case EVR_DS: + return ValueRepresentation_DecimalString; + + case EVR_DT: + return ValueRepresentation_DateTime; + + case EVR_FL: + return ValueRepresentation_FloatingPointSingle; + + case EVR_FD: + return ValueRepresentation_FloatingPointDouble; + + case EVR_IS: + return ValueRepresentation_IntegerString; + + case EVR_LO: + return ValueRepresentation_LongString; + + case EVR_LT: + return ValueRepresentation_LongText; + + case EVR_OB: + return ValueRepresentation_OtherByte; + +#if DCMTK_VERSION_NUMBER >= 361 + case EVR_OD: + return ValueRepresentation_OtherDouble; +#endif + + case EVR_OF: + return ValueRepresentation_OtherFloat; + +#if DCMTK_VERSION_NUMBER >= 362 + case EVR_OL: + return ValueRepresentation_OtherLong; +#endif + + case EVR_OW: + return ValueRepresentation_OtherWord; + + case EVR_PN: + return ValueRepresentation_PersonName; + + case EVR_SH: + return ValueRepresentation_ShortString; + + case EVR_SL: + return ValueRepresentation_SignedLong; + + case EVR_SQ: + return ValueRepresentation_Sequence; + + case EVR_SS: + return ValueRepresentation_SignedShort; + + case EVR_ST: + return ValueRepresentation_ShortText; + + case EVR_TM: + return ValueRepresentation_Time; + +#if DCMTK_VERSION_NUMBER >= 361 + case EVR_UC: + return ValueRepresentation_UnlimitedCharacters; +#endif + + case EVR_UI: + return ValueRepresentation_UniqueIdentifier; + + case EVR_UL: + return ValueRepresentation_UnsignedLong; + + case EVR_UN: + return ValueRepresentation_Unknown; + +#if DCMTK_VERSION_NUMBER >= 361 + case EVR_UR: + return ValueRepresentation_UniversalResource; +#endif + + case EVR_US: + return ValueRepresentation_UnsignedShort; + + case EVR_UT: + return ValueRepresentation_UnlimitedText; + + default: + return ValueRepresentation_NotSupported; + } + } + + + DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag, + const std::string& privateCreator) + { + if (tag.IsPrivate() && + privateCreator.empty()) + { + // This solves issue 140 (Modifying private tags with REST API + // changes VR from LO to UN) + // https://bitbucket.org/sjodogne/orthanc/issues/140 + LOG(WARNING) << "Private creator should not be empty while creating a private tag: " << tag.Format(); + } + +#if DCMTK_VERSION_NUMBER >= 362 + DcmTag key(tag.GetGroup(), tag.GetElement()); + if (tag.IsPrivate()) + { + return DcmItem::newDicomElement(key, privateCreator.c_str()); + } + else + { + return DcmItem::newDicomElement(key, NULL); + } + +#else + DcmTag key(tag.GetGroup(), tag.GetElement()); + if (tag.IsPrivate()) + { + // https://forum.dcmtk.org/viewtopic.php?t=4527 + LOG(WARNING) << "You are using DCMTK <= 3.6.1: All the private tags " + "are considered as having a binary value representation"; + key.setPrivateCreator(privateCreator.c_str()); + return new DcmOtherByteOtherWord(key); + } + else + { + return newDicomElement(key); + } +#endif + } + + + + void FromDcmtkBridge::FillElementWithString(DcmElement& element, + const std::string& utf8Value, + bool decodeDataUriScheme, + Encoding dicomEncoding) + { + 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 if (dicomEncoding != Encoding_Utf8) + { + binary = Toolbox::ConvertFromUtf8(utf8Value, dicomEncoding); + decoded = &binary; + } + + if (IsBinaryTag(element.getTag())) + { + bool ok; + + switch (element.getTag().getEVR()) + { + case EVR_OW: + if (decoded->size() % sizeof(Uint16) != 0) + { + LOG(ERROR) << "A tag with OW VR must have an even number of bytes"; + ok = false; + } + else + { + ok = element.putUint16Array((const Uint16*) decoded->c_str(), decoded->size() / sizeof(Uint16)).good(); + } + + break; + + default: + ok = element.putUint8Array((const Uint8*) decoded->c_str(), decoded->size()).good(); + break; + } + + if (ok) + { + return; + } + else + { + throw OrthancException(ErrorCode_InternalError); + } + } + + bool ok = false; + + try + { + switch (element.getTag().getEVR()) + { + // http://support.dcmtk.org/docs/dcvr_8h-source.html + + /** + * TODO. + **/ + + case EVR_OB: // other byte + case EVR_OW: // other word + case EVR_AT: // attribute tag + throw OrthancException(ErrorCode_NotImplemented); + + case EVR_UN: // unknown value representation + throw OrthancException(ErrorCode_ParameterOutOfRange); + + + /** + * String types. + **/ + + case EVR_DS: // decimal string + case EVR_IS: // integer string + case EVR_AS: // age string + case EVR_DA: // date string + case EVR_DT: // date time string + case EVR_TM: // time string + case EVR_AE: // application entity title + case EVR_CS: // code string + case EVR_SH: // short string + case EVR_LO: // long string + case EVR_ST: // short text + case EVR_LT: // long text + case EVR_UT: // unlimited text + case EVR_PN: // person name + case EVR_UI: // unique identifier +#if DCMTK_VERSION_NUMBER >= 361 + case EVR_UC: // unlimited characters + case EVR_UR: // URI/URL +#endif + { + ok = element.putString(decoded->c_str()).good(); + break; + } + + + /** + * Numerical types + **/ + + case EVR_SL: // signed long + { + ok = element.putSint32(boost::lexical_cast<Sint32>(*decoded)).good(); + break; + } + + case EVR_SS: // signed short + { + ok = element.putSint16(boost::lexical_cast<Sint16>(*decoded)).good(); + break; + } + + case EVR_UL: // unsigned long +#if DCMTK_VERSION_NUMBER >= 362 + case EVR_OL: // other long (requires byte-swapping) +#endif + { + ok = element.putUint32(boost::lexical_cast<Uint32>(*decoded)).good(); + break; + } + + case EVR_US: // unsigned short + { + ok = element.putUint16(boost::lexical_cast<Uint16>(*decoded)).good(); + break; + } + + case EVR_FL: // float single-precision + case EVR_OF: // other float (requires byte swapping) + { + ok = element.putFloat32(boost::lexical_cast<float>(*decoded)).good(); + break; + } + + case EVR_FD: // float double-precision +#if DCMTK_VERSION_NUMBER >= 361 + case EVR_OD: // other double (requires byte-swapping) +#endif + { + ok = element.putFloat64(boost::lexical_cast<double>(*decoded)).good(); + break; + } + + + /** + * Sequence types, should never occur at this point. + **/ + + case EVR_SQ: // sequence of items + { + ok = false; + break; + } + + + /** + * Internal to DCMTK. + **/ + + case EVR_ox: // OB or OW depending on context + case EVR_xs: // SS or US depending on context + case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) + case EVR_na: // na="not applicable", for data which has no VR + case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor + case EVR_item: // used internally for items + case EVR_metainfo: // used internally for meta info datasets + case EVR_dataset: // used internally for datasets + case EVR_fileFormat: // used internally for DICOM files + case EVR_dicomDir: // used internally for DICOMDIR objects + case EVR_dirRecord: // used internally for DICOMDIR records + case EVR_pixelSQ: // used internally for pixel sequences in a compressed image + case EVR_pixelItem: // used internally for pixel items in a compressed image + case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) + case EVR_PixelData: // used internally for uncompressed pixeld data + case EVR_OverlayData: // used internally for overlay data + case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR + default: + break; + } + } + catch (boost::bad_lexical_cast&) + { + ok = false; + } + + if (!ok) + { + DicomTag tag(element.getTag().getGroup(), element.getTag().getElement()); + throw OrthancException(ErrorCode_BadFileFormat, + "While creating a DICOM instance, tag (" + tag.Format() + + ") has out-of-range value: \"" + (*decoded) + "\""); + } + } + + + DcmElement* FromDcmtkBridge::FromJson(const DicomTag& tag, + const Json::Value& value, + bool decodeDataUriScheme, + Encoding dicomEncoding, + const std::string& privateCreator) + { + std::unique_ptr<DcmElement> element; + + switch (value.type()) + { + case Json::stringValue: + element.reset(CreateElementForTag(tag, privateCreator)); + FillElementWithString(*element, value.asString(), decodeDataUriScheme, dicomEncoding); + break; + + case Json::nullValue: + element.reset(CreateElementForTag(tag, privateCreator)); + FillElementWithString(*element, "", decodeDataUriScheme, dicomEncoding); + break; + + case Json::arrayValue: + { + const char* p = NULL; + if (tag.IsPrivate() && + !privateCreator.empty()) + { + p = privateCreator.c_str(); + } + + DcmTag key(tag.GetGroup(), tag.GetElement(), p); + if (key.getEVR() != EVR_SQ) + { + throw OrthancException(ErrorCode_BadParameterType, + "Bad Parameter type for tag " + tag.Format()); + } + + DcmSequenceOfItems* sequence = new DcmSequenceOfItems(key); + element.reset(sequence); + + for (Json::Value::ArrayIndex i = 0; i < value.size(); i++) + { + std::unique_ptr<DcmItem> item(new DcmItem); + + switch (value[i].type()) + { + case Json::objectValue: + { + Json::Value::Members members = value[i].getMemberNames(); + for (Json::Value::ArrayIndex j = 0; j < members.size(); j++) + { + item->insert(FromJson(ParseTag(members[j]), value[i][members[j]], decodeDataUriScheme, dicomEncoding, privateCreator)); + } + break; + } + + case Json::arrayValue: + { + // Lua cannot disambiguate between an empty dictionary + // and an empty array + if (value[i].size() != 0) + { + throw OrthancException(ErrorCode_BadParameterType); + } + break; + } + + default: + throw OrthancException(ErrorCode_BadParameterType); + } + + sequence->append(item.release()); + } + + break; + } + + default: + throw OrthancException(ErrorCode_BadParameterType, "Bad Parameter type for tag " + tag.Format()); + } + + return element.release(); + } + + + DcmPixelSequence* FromDcmtkBridge::GetPixelSequence(DcmDataset& dataset) + { + DcmElement *element = NULL; + if (!dataset.findAndGetElement(DCM_PixelData, element).good()) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element); + + E_TransferSyntax repType; + const DcmRepresentationParameter *repParam = NULL; + pixelData.getCurrentRepresentationKey(repType, repParam); + + DcmPixelSequence* pixelSequence = NULL; + if (!pixelData.getEncapsulatedRepresentation(repType, repParam, pixelSequence).good()) + { + return NULL; + } + else + { + return pixelSequence; + } + } + + + Encoding FromDcmtkBridge::ExtractEncoding(const Json::Value& json, + Encoding defaultEncoding) + { + if (json.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + Encoding encoding = defaultEncoding; + + const Json::Value::Members tags = json.getMemberNames(); + + // Look for SpecificCharacterSet (0008,0005) in the JSON file + for (size_t i = 0; i < tags.size(); i++) + { + DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]); + if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET) + { + const Json::Value& value = json[tags[i]]; + if (value.type() != Json::stringValue || + (value.asString().length() != 0 && + !GetDicomEncoding(encoding, value.asCString()))) + { + throw OrthancException(ErrorCode_BadRequest, + "Unknown encoding while creating DICOM from JSON: " + + value.toStyledString()); + } + + if (value.asString().length() == 0) + { + return defaultEncoding; + } + } + } + + return encoding; + } + + + static void SetString(DcmDataset& target, + const DcmTag& tag, + const std::string& value) + { + if (!target.putAndInsertString(tag, value.c_str()).good()) + { + throw OrthancException(ErrorCode_InternalError); + } + } + + + DcmDataset* FromDcmtkBridge::FromJson(const Json::Value& json, // Encoded using UTF-8 + bool generateIdentifiers, + bool decodeDataUriScheme, + Encoding defaultEncoding, + const std::string& privateCreator) + { + std::unique_ptr<DcmDataset> result(new DcmDataset); + Encoding encoding = ExtractEncoding(json, defaultEncoding); + + SetString(*result, DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(encoding)); + + const Json::Value::Members tags = json.getMemberNames(); + + bool hasPatientId = false; + bool hasStudyInstanceUid = false; + bool hasSeriesInstanceUid = false; + bool hasSopInstanceUid = false; + + 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_PATIENT_ID) + { + hasPatientId = true; + } + else if (tag == DICOM_TAG_STUDY_INSTANCE_UID) + { + hasStudyInstanceUid = true; + } + else if (tag == DICOM_TAG_SERIES_INSTANCE_UID) + { + hasSeriesInstanceUid = true; + } + else if (tag == DICOM_TAG_SOP_INSTANCE_UID) + { + hasSopInstanceUid = true; + } + + if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET) + { + std::unique_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator)); + const DcmTagKey& tag = element->getTag(); + + result->findAndDeleteElement(tag); + + DcmElement* tmp = element.release(); + if (!result->insert(tmp, false, false).good()) + { + delete tmp; + throw OrthancException(ErrorCode_InternalError); + } + } + } + + if (!hasPatientId && + generateIdentifiers) + { + SetString(*result, DCM_PatientID, GenerateUniqueIdentifier(ResourceType_Patient)); + } + + if (!hasStudyInstanceUid && + generateIdentifiers) + { + SetString(*result, DCM_StudyInstanceUID, GenerateUniqueIdentifier(ResourceType_Study)); + } + + if (!hasSeriesInstanceUid && + generateIdentifiers) + { + SetString(*result, DCM_SeriesInstanceUID, GenerateUniqueIdentifier(ResourceType_Series)); + } + + if (!hasSopInstanceUid && + generateIdentifiers) + { + SetString(*result, DCM_SOPInstanceUID, GenerateUniqueIdentifier(ResourceType_Instance)); + } + + return result.release(); + } + + + DcmFileFormat* FromDcmtkBridge::LoadFromMemoryBuffer(const void* buffer, + size_t size) + { + DcmInputBufferStream is; + if (size > 0) + { + is.setBuffer(buffer, size); + } + is.setEos(); + + std::unique_ptr<DcmFileFormat> result(new DcmFileFormat); + + result->transferInit(); + + /** + * New in Orthanc 1.6.0: The "size" is given as an argument to the + * "read()" method. This can avoid huge memory consumption if + * parsing an invalid DICOM file, which can notably been observed + * by executing the integration test "test_upload_compressed" on + * valgrind running Orthanc. + **/ + if (!result->read(is, EXS_Unknown, EGL_noChange, size).good()) + { + throw OrthancException(ErrorCode_BadFileFormat, + "Cannot parse an invalid DICOM file (size: " + + boost::lexical_cast<std::string>(size) + " bytes)"); + } + + result->loadAllDataIntoMemory(); + result->transferEnd(); + + return result.release(); + } + + + void FromDcmtkBridge::FromJson(DicomMap& target, + const Json::Value& source) + { + if (source.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + target.Clear(); + + Json::Value::Members members = source.getMemberNames(); + + for (size_t i = 0; i < members.size(); i++) + { + const Json::Value& value = source[members[i]]; + + if (value.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + target.SetValue(ParseTag(members[i]), value.asString(), false); + } + } + + + void FromDcmtkBridge::ChangeStringEncoding(DcmItem& dataset, + Encoding source, + bool hasSourceCodeExtensions, + Encoding target) + { + // Recursive exploration of a dataset to change the encoding of + // each string-like element + + if (source == target) + { + return; + } + + for (unsigned long i = 0; i < dataset.card(); i++) + { + DcmElement* element = dataset.getElement(i); + if (element) + { + if (element->isLeaf()) + { + char *c = NULL; + if (element->isaString() && + element->getString(c).good() && + c != NULL) + { + std::string a = Toolbox::ConvertToUtf8(c, source, hasSourceCodeExtensions); + std::string b = Toolbox::ConvertFromUtf8(a, target); + element->putString(b.c_str()); + } + } + else + { + // "All subclasses of DcmElement except for DcmSequenceOfItems + // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset + // etc. are not." The following dynamic_cast is thus OK. + DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element); + + for (unsigned long j = 0; j < sequence.card(); j++) + { + ChangeStringEncoding(*sequence.getItem(j), source, hasSourceCodeExtensions, target); + } + } + } + } + } + + +#if ORTHANC_ENABLE_LUA == 1 + void FromDcmtkBridge::ExecuteToDicom(DicomMap& target, + LuaFunctionCall& call) + { + Json::Value output; + call.ExecuteToJson(output, true /* keep strings */); + + target.Clear(); + + if (output.type() == Json::arrayValue && + output.size() == 0) + { + // This case happens for empty tables + return; + } + + if (output.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_LuaBadOutput, + "Lua: The script must return a table"); + } + + Json::Value::Members members = output.getMemberNames(); + + for (size_t i = 0; i < members.size(); i++) + { + if (output[members[i]].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_LuaBadOutput, + "Lua: The script must return a table " + "mapping names of DICOM tags to strings"); + } + + DicomTag tag(ParseTag(members[i])); + target.SetValue(tag, output[members[i]].asString(), false); + } + } +#endif + + + void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target, + DcmItem& dataset, + const std::set<DicomTag>& ignoreTagLength) + { + ExtractDicomSummary(target, dataset, + ORTHANC_MAXIMUM_TAG_LENGTH, + GetDefaultDicomEncoding(), ignoreTagLength); + } + + + void FromDcmtkBridge::ExtractDicomAsJson(Json::Value& target, + DcmDataset& dataset, + const std::set<DicomTag>& ignoreTagLength) + { + ExtractDicomAsJson(target, dataset, + DicomToJsonFormat_Full, + DicomToJsonFlags_Default, + ORTHANC_MAXIMUM_TAG_LENGTH, + GetDefaultDicomEncoding(), + ignoreTagLength); + } + + + void FromDcmtkBridge::InitializeCodecs() + { +#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 + LOG(INFO) << "Registering JPEG Lossless codecs in DCMTK"; + DJLSDecoderRegistration::registerCodecs(); +# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + DJLSEncoderRegistration::registerCodecs(); +# endif +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG == 1 + LOG(INFO) << "Registering JPEG codecs in DCMTK"; + DJDecoderRegistration::registerCodecs(); +# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + DJEncoderRegistration::registerCodecs(); +# endif +#endif + + LOG(INFO) << "Registering RLE codecs in DCMTK"; + DcmRLEDecoderRegistration::registerCodecs(); +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + DcmRLEEncoderRegistration::registerCodecs(); +#endif + } + + + void FromDcmtkBridge::FinalizeCodecs() + { +#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 + // Unregister JPEG-LS codecs + DJLSDecoderRegistration::cleanup(); +# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + DJLSEncoderRegistration::cleanup(); +# endif +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG == 1 + // Unregister JPEG codecs + DJDecoderRegistration::cleanup(); +# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + DJEncoderRegistration::cleanup(); +# endif +#endif + + DcmRLEDecoderRegistration::cleanup(); +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + DcmRLEEncoderRegistration::cleanup(); +#endif + } + + + + // Forward declaration + static void ApplyVisitorToElement(DcmElement& element, + ITagVisitor& visitor, + const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + Encoding encoding, + bool hasCodeExtensions); + + static void ApplyVisitorToDataset(DcmItem& dataset, + ITagVisitor& visitor, + const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + Encoding encoding, + bool hasCodeExtensions) + { + assert(parentTags.size() == parentIndexes.size()); + + for (unsigned long i = 0; i < dataset.card(); i++) + { + DcmElement* element = dataset.getElement(i); + if (element == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + ApplyVisitorToElement(*element, visitor, parentTags, parentIndexes, encoding, hasCodeExtensions); + } + } + } + + + static void ApplyVisitorToLeaf(DcmElement& element, + ITagVisitor& visitor, + const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + Encoding encoding, + bool hasCodeExtensions) + { + // TODO - Merge this function, that is more recent, with ConvertLeafElement() + + assert(element.isLeaf()); + + DcmEVR evr = element.getTag().getEVR(); + + + /** + * Fix the EVR for types internal to DCMTK + **/ + + if (evr == EVR_ox) // OB or OW depending on context + { + evr = EVR_OB; + } + + if (evr == EVR_UNKNOWN || // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) + evr == EVR_UNKNOWN2B) // used internally for elements with unknown VR with 2-byte length field in explicit VR + { + evr = EVR_UN; + } + + const ValueRepresentation vr = FromDcmtkBridge::Convert(evr); + + + /** + * Deal with binary data (including PixelData). + **/ + + if (evr == EVR_OB || // other byte + evr == EVR_OW || // other word + evr == EVR_UN) // unknown value representation + { + Uint16* data16 = NULL; + Uint8* data = NULL; + + if (evr == EVR_OW && + element.getUint16Array(data16) == EC_Normal) + { + visitor.VisitBinary(parentTags, parentIndexes, tag, vr, data16, element.getLength()); + } + else if (evr != EVR_OW && + element.getUint8Array(data) == EC_Normal) + { + visitor.VisitBinary(parentTags, parentIndexes, tag, vr, data, element.getLength()); + } + else + { + visitor.VisitNotSupported(parentTags, parentIndexes, tag, vr); + } + + return; // We're done + } + + + /** + * Deal with plain strings (and convert them to UTF-8) + **/ + + char *c = NULL; + if (element.isaString() && + element.getString(c).good()) + { + std::string utf8; + + if (c != NULL) // This case corresponds to the empty string + { + if (element.getTag() == DCM_SpecificCharacterSet) + { + utf8.assign(c); + } + else + { + std::string s(c); + utf8 = Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions); + } + } + + std::string newValue; + ITagVisitor::Action action = visitor.VisitString + (newValue, parentTags, parentIndexes, tag, vr, utf8); + + switch (action) + { + case ITagVisitor::Action_None: + break; + + case ITagVisitor::Action_Replace: + { + std::string s = Toolbox::ConvertFromUtf8(newValue, encoding); + if (element.putString(s.c_str()) != EC_Normal) + { + throw OrthancException(ErrorCode_InternalError, + "Cannot replace value of tag: " + tag.Format()); + } + + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + + return; // We're done + } + + + try + { + // http://support.dcmtk.org/docs/dcvr_8h-source.html + switch (evr) + { + + /** + * Plain string values. + **/ + + case EVR_DS: // decimal string + case EVR_IS: // integer string + case EVR_AS: // age string + case EVR_DA: // date string + case EVR_DT: // date time string + case EVR_TM: // time string + case EVR_AE: // application entity title + case EVR_CS: // code string + case EVR_SH: // short string + case EVR_LO: // long string + case EVR_ST: // short text + case EVR_LT: // long text + case EVR_UT: // unlimited text + case EVR_PN: // person name + case EVR_UI: // unique identifier + { + Uint8* data = NULL; + + if (element.getUint8Array(data) == EC_Normal) + { + const Uint32 length = element.getLength(); + Uint32 l = 0; + while (l < length && + data[l] != 0) + { + l++; + } + + if (l == length) + { + // Not a null-terminated plain string + visitor.VisitNotSupported(parentTags, parentIndexes, tag, vr); + } + else + { + std::string ignored; + std::string s(reinterpret_cast<const char*>(data), l); + ITagVisitor::Action action = visitor.VisitString + (ignored, parentTags, parentIndexes, tag, vr, + Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions)); + + if (action != ITagVisitor::Action_None) + { + LOG(WARNING) << "Cannot replace this string tag: " + << FromDcmtkBridge::GetTagName(element) + << " (" << tag.Format() << ")"; + } + } + } + else + { + visitor.VisitNotSupported(parentTags, parentIndexes, tag, vr); + } + + return; + } + + /** + * Numeric types + **/ + + case EVR_SL: // signed long + { + DcmSignedLong& content = dynamic_cast<DcmSignedLong&>(element); + + std::vector<int64_t> values; + values.reserve(content.getVM()); + + for (unsigned long i = 0; i < content.getVM(); i++) + { + Sint32 f; + if (content.getSint32(f, i).good()) + { + values.push_back(f); + } + } + + visitor.VisitIntegers(parentTags, parentIndexes, tag, vr, values); + break; + } + + case EVR_SS: // signed short + { + DcmSignedShort& content = dynamic_cast<DcmSignedShort&>(element); + + std::vector<int64_t> values; + values.reserve(content.getVM()); + + for (unsigned long i = 0; i < content.getVM(); i++) + { + Sint16 f; + if (content.getSint16(f, i).good()) + { + values.push_back(f); + } + } + + visitor.VisitIntegers(parentTags, parentIndexes, tag, vr, values); + break; + } + + case EVR_UL: // unsigned long +#if DCMTK_VERSION_NUMBER >= 362 + case EVR_OL: +#endif + { + DcmUnsignedLong& content = dynamic_cast<DcmUnsignedLong&>(element); + + std::vector<int64_t> values; + values.reserve(content.getVM()); + + for (unsigned long i = 0; i < content.getVM(); i++) + { + Uint32 f; + if (content.getUint32(f, i).good()) + { + values.push_back(f); + } + } + + visitor.VisitIntegers(parentTags, parentIndexes, tag, vr, values); + break; + } + + case EVR_US: // unsigned short + { + DcmUnsignedShort& content = dynamic_cast<DcmUnsignedShort&>(element); + + std::vector<int64_t> values; + values.reserve(content.getVM()); + + for (unsigned long i = 0; i < content.getVM(); i++) + { + Uint16 f; + if (content.getUint16(f, i).good()) + { + values.push_back(f); + } + } + + visitor.VisitIntegers(parentTags, parentIndexes, tag, vr, values); + break; + } + + case EVR_FL: // float single-precision + case EVR_OF: + { + DcmFloatingPointSingle& content = dynamic_cast<DcmFloatingPointSingle&>(element); + + std::vector<double> values; + values.reserve(content.getVM()); + + for (unsigned long i = 0; i < content.getVM(); i++) + { + Float32 f; + if (content.getFloat32(f, i).good()) + { + values.push_back(f); + } + } + + visitor.VisitDoubles(parentTags, parentIndexes, tag, vr, values); + break; + } + + case EVR_FD: // float double-precision +#if DCMTK_VERSION_NUMBER >= 361 + case EVR_OD: +#endif + { + DcmFloatingPointDouble& content = dynamic_cast<DcmFloatingPointDouble&>(element); + + std::vector<double> values; + values.reserve(content.getVM()); + + for (unsigned long i = 0; i < content.getVM(); i++) + { + Float64 f; + if (content.getFloat64(f, i).good()) + { + values.push_back(f); + } + } + + visitor.VisitDoubles(parentTags, parentIndexes, tag, vr, values); + break; + } + + + /** + * Attribute tag. + **/ + + case EVR_AT: + { + DcmAttributeTag& content = dynamic_cast<DcmAttributeTag&>(element); + + std::vector<DicomTag> values; + values.reserve(content.getVM()); + + for (unsigned long i = 0; i < content.getVM(); i++) + { + DcmTagKey f; + if (content.getTagVal(f, i).good()) + { + DicomTag t(f.getGroup(), f.getElement()); + values.push_back(t); + } + } + + assert(vr == ValueRepresentation_AttributeTag); + visitor.VisitAttributes(parentTags, parentIndexes, tag, values); + break; + } + + + /** + * Sequence types, should never occur at this point because of + * "element.isLeaf()". + **/ + + case EVR_SQ: // sequence of items + { + return; + } + + + /** + * Internal to DCMTK. + **/ + + case EVR_xs: // SS or US depending on context + case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) + case EVR_na: // na="not applicable", for data which has no VR + case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor + case EVR_item: // used internally for items + case EVR_metainfo: // used internally for meta info datasets + case EVR_dataset: // used internally for datasets + case EVR_fileFormat: // used internally for DICOM files + case EVR_dicomDir: // used internally for DICOMDIR objects + case EVR_dirRecord: // used internally for DICOMDIR records + case EVR_pixelSQ: // used internally for pixel sequences in a compressed image + case EVR_pixelItem: // used internally for pixel items in a compressed image + case EVR_PixelData: // used internally for uncompressed pixeld data + case EVR_OverlayData: // used internally for overlay data + { + visitor.VisitNotSupported(parentTags, parentIndexes, tag, vr); + return; + } + + + /** + * Default case. + **/ + + default: + return; + } + } + catch (boost::bad_lexical_cast&) + { + return; + } + catch (std::bad_cast&) + { + return; + } + } + + + static void ApplyVisitorToElement(DcmElement& element, + ITagVisitor& visitor, + const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + Encoding encoding, + bool hasCodeExtensions) + { + assert(parentTags.size() == parentIndexes.size()); + + DicomTag tag(FromDcmtkBridge::Convert(element.getTag())); + + if (element.isLeaf()) + { + ApplyVisitorToLeaf(element, visitor, parentTags, parentIndexes, tag, encoding, hasCodeExtensions); + } + else + { + // "All subclasses of DcmElement except for DcmSequenceOfItems + // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset + // etc. are not." The following dynamic_cast is thus OK. + DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(element); + + if (sequence.card() == 0) + { + visitor.VisitEmptySequence(parentTags, parentIndexes, tag); + } + else + { + std::vector<DicomTag> tags = parentTags; + std::vector<size_t> indexes = parentIndexes; + tags.push_back(tag); + indexes.push_back(0); + + for (unsigned long i = 0; i < sequence.card(); i++) + { + indexes.back() = static_cast<size_t>(i); + DcmItem* child = sequence.getItem(i); + ApplyVisitorToDataset(*child, visitor, tags, indexes, encoding, hasCodeExtensions); + } + } + } + } + + + void FromDcmtkBridge::Apply(DcmItem& dataset, + ITagVisitor& visitor, + Encoding defaultEncoding) + { + std::vector<DicomTag> parentTags; + std::vector<size_t> parentIndexes; + bool hasCodeExtensions; + Encoding encoding = DetectEncoding(hasCodeExtensions, dataset, defaultEncoding); + ApplyVisitorToDataset(dataset, visitor, parentTags, parentIndexes, encoding, hasCodeExtensions); + } + + + + bool FromDcmtkBridge::LookupOrthancTransferSyntax(DicomTransferSyntax& target, + DcmFileFormat& dicom) + { + if (dicom.getDataset() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + DcmDataset& dataset = *dicom.getDataset(); + + E_TransferSyntax xfer = dataset.getCurrentXfer(); + if (xfer == EXS_Unknown) + { + dataset.updateOriginalXfer(); + xfer = dataset.getOriginalXfer(); + if (xfer == EXS_Unknown) + { + throw OrthancException(ErrorCode_BadFileFormat, + "Cannot determine the transfer syntax of the DICOM instance"); + } + } + + return FromDcmtkBridge::LookupOrthancTransferSyntax(target, xfer); + } +} + + +#include "./FromDcmtkBridge_TransferSyntaxes.impl.h"