# HG changeset patch # User Sebastien Jodogne # Date 1444654078 -7200 # Node ID 4aaaecae580372883363630aa7b8873831dca01f # Parent 21d31da7337473cdde8757468cb16acc32d3e093# Parent f5ddbd9239dd1911dbb2e7f1244ce39a01febfcb integration mainline->db-changes diff -r 21d31da73374 -r 4aaaecae5803 NEWS --- a/NEWS Tue Oct 06 14:44:52 2015 +0200 +++ b/NEWS Mon Oct 12 14:47:58 2015 +0200 @@ -3,6 +3,9 @@ * Add ".dcm" suffix to files in ZIP archives (cf. URI ".../archive") * "/tools/create-dicom": Support of binary tags encoded using data URI scheme +* "/tools/create-dicom": Support of hierarchical structures (creation of sequences) +* "/modify" can insert/modify sequences +* New URIs for attachments: ".../compress", ".../uncompress" and ".../is-compressed" Plugins ------- @@ -18,6 +21,7 @@ Maintenance ----------- +* Fix issue 4 (C-Store Association not renegotiated on Specific-to-specific transfer syntax change) * "/system" URI gives information about the plugins used for storage area and DB back-end * Plugin callbacks should now return explicit "OrthancPluginErrorCode" instead of integers * "/tools/create-dicom" can create tags with unknown VR diff -r 21d31da73374 -r 4aaaecae5803 OrthancServer/DicomInstanceToStore.cpp --- a/OrthancServer/DicomInstanceToStore.cpp Tue Oct 06 14:44:52 2015 +0200 +++ b/OrthancServer/DicomInstanceToStore.cpp Mon Oct 12 14:47:58 2015 +0200 @@ -109,7 +109,8 @@ if (!json_.HasContent()) { json_.Allocate(); - FromDcmtkBridge::ToJson(json_.GetContent(), GetDataset(parsed_.GetContent())); + FromDcmtkBridge::ToJson(json_.GetContent(), GetDataset(parsed_.GetContent()), + DicomToJsonFormat_Full, 256 /* max string length */); } } diff -r 21d31da73374 -r 4aaaecae5803 OrthancServer/DicomModification.cpp --- a/OrthancServer/DicomModification.cpp Tue Oct 06 14:44:52 2015 +0200 +++ b/OrthancServer/DicomModification.cpp Mon Oct 12 14:47:58 2015 +0200 @@ -44,13 +44,56 @@ namespace Orthanc { + void DicomModification::RemoveInternal(const DicomTag& tag) + { + Replacements::iterator it = replacements_.find(tag); + + if (it != replacements_.end()) + { + delete it->second; + replacements_.erase(it); + } + } + + + void DicomModification::ReplaceInternal(const DicomTag& tag, + const Json::Value& value) + { + Replacements::iterator it = replacements_.find(tag); + + if (it != replacements_.end()) + { + delete it->second; + it->second = NULL; // In the case of an exception during the clone + it->second = new Json::Value(value); // Clone + } + else + { + replacements_[tag] = new Json::Value(value); // Clone + } + } + + + void DicomModification::ClearReplacements() + { + for (Replacements::iterator it = replacements_.begin(); + it != replacements_.end(); ++it) + { + delete it->second; + } + + replacements_.clear(); + } + + void DicomModification::MarkNotOrthancAnonymization() { Replacements::iterator it = replacements_.find(DICOM_TAG_DEIDENTIFICATION_METHOD); if (it != replacements_.end() && - it->second == ORTHANC_DEIDENTIFICATION_METHOD) + it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD) { + delete it->second; replacements_.erase(it); } } @@ -100,7 +143,7 @@ dicom.Replace(*tag, mapped); } - + DicomModification::DicomModification() { removePrivateTags_ = false; @@ -108,10 +151,15 @@ allowManualIdentifiers_ = true; } + DicomModification::~DicomModification() + { + ClearReplacements(); + } + void DicomModification::Keep(const DicomTag& tag) { removals_.erase(tag); - replacements_.erase(tag); + RemoveInternal(tag); if (FromDcmtkBridge::IsPrivateTag(tag)) { @@ -124,7 +172,7 @@ void DicomModification::Remove(const DicomTag& tag) { removals_.insert(tag); - replacements_.erase(tag); + RemoveInternal(tag); privateTagsToKeep_.erase(tag); MarkNotOrthancAnonymization(); @@ -136,12 +184,12 @@ } void DicomModification::Replace(const DicomTag& tag, - const std::string& value, + const Json::Value& value, bool safeForAnonymization) { removals_.erase(tag); privateTagsToKeep_.erase(tag); - replacements_[tag] = value; + ReplaceInternal(tag, value); if (!safeForAnonymization) { @@ -149,12 +197,13 @@ } } + bool DicomModification::IsReplaced(const DicomTag& tag) const { return replacements_.find(tag) != replacements_.end(); } - const std::string& DicomModification::GetReplacement(const DicomTag& tag) const + const Json::Value& DicomModification::GetReplacement(const DicomTag& tag) const { Replacements::const_iterator it = replacements_.find(tag); @@ -164,10 +213,26 @@ } else { - return it->second; + return *it->second; } } + + std::string DicomModification::GetReplacementAsString(const DicomTag& tag) const + { + const Json::Value& json = GetReplacement(tag); + + if (json.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadParameterType); + } + else + { + return json.asString(); + } + } + + void DicomModification::SetRemovePrivateTags(bool removed) { removePrivateTags_ = removed; @@ -192,7 +257,7 @@ void DicomModification::SetupAnonymization() { removals_.clear(); - replacements_.clear(); + ClearReplacements(); removePrivateTags_ = true; level_ = ResourceType_Patient; uidMap_.clear(); @@ -255,15 +320,15 @@ removals_.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts // Set the DeidentificationMethod tag - replacements_.insert(std::make_pair(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD)); + ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD); // Set the PatientIdentityRemoved tag - replacements_.insert(std::make_pair(DicomTag(0x0012, 0x0062), "YES")); + ReplaceInternal(DicomTag(0x0012, 0x0062), "YES"); // (*) Choose a random patient name and ID std::string patientId = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient); - replacements_[DICOM_TAG_PATIENT_ID] = patientId; - replacements_[DICOM_TAG_PATIENT_NAME] = patientId; + ReplaceInternal(DICOM_TAG_PATIENT_ID, patientId); + ReplaceInternal(DICOM_TAG_PATIENT_NAME, patientId); } void DicomModification::Apply(ParsedDicomFile& toModify) @@ -394,7 +459,7 @@ for (Replacements::const_iterator it = replacements_.begin(); it != replacements_.end(); ++it) { - toModify.Replace(it->first, it->second, DicomReplaceMode_InsertIfAbsent); + toModify.Replace(it->first, *it->second, DicomReplaceMode_InsertIfAbsent); } // (4) Update the DICOM identifiers diff -r 21d31da73374 -r 4aaaecae5803 OrthancServer/DicomModification.h --- a/OrthancServer/DicomModification.h Tue Oct 06 14:44:52 2015 +0200 +++ b/OrthancServer/DicomModification.h Mon Oct 12 14:47:58 2015 +0200 @@ -36,7 +36,7 @@ namespace Orthanc { - class DicomModification + class DicomModification : public boost::noncopyable { /** * Process: @@ -47,7 +47,7 @@ private: typedef std::set SetOfTags; - typedef std::map Replacements; + typedef std::map Replacements; typedef std::map< std::pair, std::string> UidMap; SetOfTags removals_; @@ -63,9 +63,18 @@ void MarkNotOrthancAnonymization(); + void ClearReplacements(); + + void RemoveInternal(const DicomTag& tag); + + void ReplaceInternal(const DicomTag& tag, + const Json::Value& value); + public: DicomModification(); + ~DicomModification(); + void Keep(const DicomTag& tag); void Remove(const DicomTag& tag); @@ -73,12 +82,14 @@ bool IsRemoved(const DicomTag& tag) const; void Replace(const DicomTag& tag, - const std::string& value, + const Json::Value& value, // Encoded using UTF-8 bool safeForAnonymization = false); bool IsReplaced(const DicomTag& tag) const; - const std::string& GetReplacement(const DicomTag& tag) const; + const Json::Value& GetReplacement(const DicomTag& tag) const; + + std::string GetReplacementAsString(const DicomTag& tag) const; void SetRemovePrivateTags(bool removed); diff -r 21d31da73374 -r 4aaaecae5803 OrthancServer/DicomProtocol/DicomUserConnection.cpp --- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp Tue Oct 06 14:44:52 2015 +0200 +++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp Mon Oct 12 14:47:58 2015 +0200 @@ -272,10 +272,22 @@ const std::string syntax(xfer.getXferID()); bool isGeneric = IsGenericTransferSyntax(syntax); - if (isGeneric ^ IsGenericTransferSyntax(connection.GetPreferredTransferSyntax())) + bool renegociate; + if (isGeneric) { - // Making a generic-to-specific or specific-to-generic change of - // the transfer syntax. Renegotiate the connection. + // Are we making a generic-to-specific or specific-to-generic change of + // the transfer syntax? If this is the case, renegotiate the connection. + renegociate = !IsGenericTransferSyntax(connection.GetPreferredTransferSyntax()); + } + else + { + // We are using a specific transfer syntax. Renegociate if the + // current connection does not match this transfer syntax. + renegociate = (syntax != connection.GetPreferredTransferSyntax()); + } + + if (renegociate) + { LOG(INFO) << "Change in the transfer syntax: the C-Store associated must be renegotiated"; if (isGeneric) diff -r 21d31da73374 -r 4aaaecae5803 OrthancServer/FromDcmtkBridge.cpp --- a/OrthancServer/FromDcmtkBridge.cpp Tue Oct 06 14:44:52 2015 +0200 +++ b/OrthancServer/FromDcmtkBridge.cpp Mon Oct 12 14:47:58 2015 +0200 @@ -95,6 +95,7 @@ #include #include +#include #include @@ -349,19 +350,6 @@ } - bool FromDcmtkBridge::IsPrivateTag(DcmTag& tag) - { -#if 1 - DcmTagKey tmp(tag.getGTag(), tag.getETag()); - return tmp.isPrivate(); -#else - // Implementation for Orthanc versions <= 0.8.5 - return (tag.getPrivateCreator() != NULL || - !strcmp("PrivateCreator", tag.getTagName())); // TODO - This may change with future versions of DCMTK -#endif - } - - bool FromDcmtkBridge::IsPrivateTag(const DicomTag& tag) { #if 1 @@ -575,127 +563,190 @@ } - static void StoreElement(Json::Value& target, - DcmElement& element, - unsigned int maxStringLength, - Encoding encoding); - - static void StoreItem(Json::Value& target, - DcmItem& item, - unsigned int maxStringLength, - Encoding encoding) + static Json::Value& PrepareNode(Json::Value& parent, + DcmElement& element, + DicomToJsonFormat format) { - target = Json::Value(Json::objectValue); - - for (unsigned long i = 0; i < item.card(); i++) - { - DcmElement* element = item.getElement(i); - StoreElement(target, *element, maxStringLength, encoding); - } - } - - - static void StoreElement(Json::Value& target, - DcmElement& element, - unsigned int maxStringLength, - Encoding encoding) - { - assert(target.type() == Json::objectValue); + assert(parent.type() == Json::objectValue); DicomTag tag(FromDcmtkBridge::GetTag(element)); const std::string formattedTag = tag.Format(); -#if 0 - const std::string tagName = FromDcmtkBridge::GetName(tag); -#else - // This version of the code gives access to the name of the private tags + if (format == DicomToJsonFormat_Short) + { + parent[formattedTag] = Json::nullValue; + return parent[formattedTag]; + } + + // This code gives access to the name of the private tags DcmTag tagbis(element.getTag()); const std::string tagName(tagbis.getTagName()); -#endif + + switch (format) + { + case DicomToJsonFormat_Simple: + 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 (tagbis.getPrivateCreator() != NULL) + { + node["PrivateCreator"] = tagbis.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, + unsigned int maxStringLength) + { + std::string content = value.AsString(); + + switch (format) + { + case DicomToJsonFormat_Short: + case DicomToJsonFormat_Simple: + { + assert(target.type() == Json::nullValue); + + if (!value.IsNull() && + (maxStringLength == 0 || + content.size() <= maxStringLength)) + { + target = content; + } + + break; + } + + case DicomToJsonFormat_Full: + { + assert(target.type() == Json::objectValue); + + if (value.IsNull()) + { + target["Type"] = "Null"; + target["Value"] = Json::nullValue; + } + else + { + if (maxStringLength == 0 || + content.size() <= maxStringLength) + { + target["Type"] = "String"; + target["Value"] = content; + } + else + { + target["Type"] = "TooLong"; + target["Value"] = Json::nullValue; + } + } + break; + } + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + static void DatasetToJson(Json::Value& parent, + DcmItem& item, + DicomToJsonFormat format, + unsigned int maxStringLength, + Encoding encoding); + + + void FromDcmtkBridge::ToJson(Json::Value& parent, + DcmElement& element, + DicomToJsonFormat format, + unsigned int maxStringLength, + Encoding encoding) + { + if (parent.type() == Json::nullValue) + { + parent = Json::objectValue; + } + + assert(parent.type() == Json::objectValue); + Json::Value& target = PrepareNode(parent, element, format); if (element.isLeaf()) { - Json::Value value(Json::objectValue); - value["Name"] = tagName; - - if (tagbis.getPrivateCreator() != NULL) - { - value["PrivateCreator"] = tagbis.getPrivateCreator(); - } - std::auto_ptr v(FromDcmtkBridge::ConvertLeafElement(element, encoding)); - if (v->IsNull()) - { - value["Type"] = "Null"; - value["Value"] = Json::nullValue; - } - else - { - std::string s = v->AsString(); - if (maxStringLength == 0 || - s.size() <= maxStringLength) - { - value["Type"] = "String"; - value["Value"] = s; - } - else - { - value["Type"] = "TooLong"; - value["Value"] = Json::nullValue; - } - } - - target[formattedTag] = value; + LeafValueToJson(target, *v, format, maxStringLength); } else { - Json::Value children(Json::arrayValue); + 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 cast is thus OK. + // etc. are not." The following dynamic_cast is thus OK. DcmSequenceOfItems& sequence = dynamic_cast(element); for (unsigned long i = 0; i < sequence.card(); i++) { DcmItem* child = sequence.getItem(i); - Json::Value& v = children.append(Json::objectValue); - StoreItem(v, *child, maxStringLength, encoding); - } - - target[formattedTag]["Name"] = tagName; - target[formattedTag]["Type"] = "Sequence"; - target[formattedTag]["Value"] = children; + Json::Value& v = target.append(Json::objectValue); + DatasetToJson(v, *child, format, maxStringLength, encoding); + } } } - void FromDcmtkBridge::ToJson(Json::Value& root, - DcmDataset& dataset, - unsigned int maxStringLength) + static void DatasetToJson(Json::Value& parent, + DcmItem& item, + DicomToJsonFormat format, + unsigned int maxStringLength, + Encoding encoding) { - StoreItem(root, dataset, maxStringLength, DetectEncoding(dataset)); + assert(parent.type() == Json::objectValue); + + for (unsigned long i = 0; i < item.card(); i++) + { + DcmElement* element = item.getElement(i); + FromDcmtkBridge::ToJson(parent, *element, format, maxStringLength, encoding); + } } - void FromDcmtkBridge::ToJson(Json::Value& target, - const std::string& path, + DcmDataset& dataset, + DicomToJsonFormat format, unsigned int maxStringLength) { - DcmFileFormat dicom; - if (!dicom.loadFile(path.c_str()).good()) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - else - { - FromDcmtkBridge::ToJson(target, *dicom.getDataset(), maxStringLength); - } + target = Json::objectValue; + DatasetToJson(target, dataset, format, maxStringLength, DetectEncoding(dataset)); } - std::string FromDcmtkBridge::GetName(const DicomTag& t) { // Some patches for important tags because of different DICOM @@ -947,4 +998,369 @@ } } + + static bool IsBinaryTag(const DcmTag& key) + { + return key.isPrivate() || key.isUnknownVR(); + } + + + DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag) + { + DcmTag key(tag.GetGroup(), tag.GetElement()); + + if (IsBinaryTag(key)) + { + return new DcmOtherByteOtherWord(key); + } + + switch (key.getEVR()) + { + // http://support.dcmtk.org/docs/dcvr_8h-source.html + + /** + * TODO. + **/ + + case EVR_OB: // other byte + case EVR_OF: // other float + 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. + * http://support.dcmtk.org/docs/classDcmByteString.html + **/ + + case EVR_AS: // age string + return new DcmAgeString(key); + + case EVR_AE: // application entity title + return new DcmApplicationEntity(key); + + case EVR_CS: // code string + return new DcmCodeString(key); + + case EVR_DA: // date string + return new DcmDate(key); + + case EVR_DT: // date time string + return new DcmDateTime(key); + + case EVR_DS: // decimal string + return new DcmDecimalString(key); + + case EVR_IS: // integer string + return new DcmIntegerString(key); + + case EVR_TM: // time string + return new DcmTime(key); + + case EVR_UI: // unique identifier + return new DcmUniqueIdentifier(key); + + case EVR_ST: // short text + return new DcmShortText(key); + + case EVR_LO: // long string + return new DcmLongString(key); + + case EVR_LT: // long text + return new DcmLongText(key); + + case EVR_UT: // unlimited text + return new DcmUnlimitedText(key); + + case EVR_SH: // short string + return new DcmShortString(key); + + case EVR_PN: // person name + return new DcmPersonName(key); + + + /** + * Numerical types + **/ + + case EVR_SL: // signed long + return new DcmSignedLong(key); + + case EVR_SS: // signed short + return new DcmSignedShort(key); + + case EVR_UL: // unsigned long + return new DcmUnsignedLong(key); + + case EVR_US: // unsigned short + return new DcmUnsignedShort(key); + + case EVR_FL: // float single-precision + return new DcmFloatingPointSingle(key); + + case EVR_FD: // float double-precision + return new DcmFloatingPointDouble(key); + + + /** + * Sequence types, should never occur at this point. + **/ + + case EVR_SQ: // sequence of items + throw OrthancException(ErrorCode_ParameterOutOfRange); + + + /** + * 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; + } + + throw OrthancException(ErrorCode_InternalError); + } + + + + void FromDcmtkBridge::FillElementWithString(DcmElement& element, + const DicomTag& tag, + const std::string& utf8Value, + bool decodeBinaryTags, + Encoding dicomEncoding) + { + std::string binary; + const std::string* decoded = &utf8Value; + + if (decodeBinaryTags && + boost::starts_with(utf8Value, "data:application/octet-stream;base64,")) + { + std::string mime; + Toolbox::DecodeDataUriScheme(mime, binary, utf8Value); + decoded = &binary; + } + else if (dicomEncoding != Encoding_Utf8) + { + binary = Toolbox::ConvertFromUtf8(utf8Value, dicomEncoding); + decoded = &binary; + } + + DcmTag key(tag.GetGroup(), tag.GetElement()); + + if (IsBinaryTag(key)) + { + if (element.putUint8Array((const Uint8*) decoded->c_str(), decoded->size()).good()) + { + return; + } + else + { + throw OrthancException(ErrorCode_InternalError); + } + } + + bool ok = false; + + try + { + switch (key.getEVR()) + { + // http://support.dcmtk.org/docs/dcvr_8h-source.html + + /** + * TODO. + **/ + + case EVR_OB: // other byte + case EVR_OF: // other float + 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 + { + ok = element.putString(decoded->c_str()).good(); + break; + } + + + /** + * Numerical types + **/ + + case EVR_SL: // signed long + { + ok = element.putSint32(boost::lexical_cast(*decoded)).good(); + break; + } + + case EVR_SS: // signed short + { + ok = element.putSint16(boost::lexical_cast(*decoded)).good(); + break; + } + + case EVR_UL: // unsigned long + { + ok = element.putUint32(boost::lexical_cast(*decoded)).good(); + break; + } + + case EVR_US: // unsigned short + { + ok = element.putUint16(boost::lexical_cast(*decoded)).good(); + break; + } + + case EVR_FL: // float single-precision + { + ok = element.putFloat32(boost::lexical_cast(*decoded)).good(); + break; + } + + case EVR_FD: // float double-precision + { + ok = element.putFloat64(boost::lexical_cast(*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) + { + throw OrthancException(ErrorCode_InternalError); + } + } + + + DcmElement* FromDcmtkBridge::FromJson(const DicomTag& tag, + const Json::Value& value, + bool decodeBinaryTags, + Encoding dicomEncoding) + { + std::auto_ptr element; + + switch (value.type()) + { + case Json::stringValue: + element.reset(CreateElementForTag(tag)); + FillElementWithString(*element, tag, value.asString(), decodeBinaryTags, dicomEncoding); + break; + + case Json::arrayValue: + { + DcmTag key(tag.GetGroup(), tag.GetElement()); + if (key.getEVR() != EVR_SQ) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + DcmSequenceOfItems* sequence = new DcmSequenceOfItems(key, value.size()); + element.reset(sequence); + + for (Json::Value::ArrayIndex i = 0; i < value.size(); i++) + { + std::auto_ptr item(new DcmItem); + + 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]], decodeBinaryTags, dicomEncoding)); + } + + sequence->append(item.release()); + } + + break; + } + + default: + throw OrthancException(ErrorCode_BadParameterType); + } + + return element.release(); + } } diff -r 21d31da73374 -r 4aaaecae5803 OrthancServer/FromDcmtkBridge.h --- a/OrthancServer/FromDcmtkBridge.h Tue Oct 06 14:44:52 2015 +0200 +++ b/OrthancServer/FromDcmtkBridge.h Mon Oct 12 14:47:58 2015 +0200 @@ -60,8 +60,6 @@ static DicomTag GetTag(const DcmElement& element); - static bool IsPrivateTag(DcmTag& tag); - static bool IsPrivateTag(const DicomTag& tag); static bool IsUnknownTag(const DicomTag& tag); @@ -69,13 +67,16 @@ static DicomValue* ConvertLeafElement(DcmElement& element, Encoding encoding); + static void ToJson(Json::Value& parent, + DcmElement& element, + DicomToJsonFormat format, + unsigned int maxStringLength, + Encoding encoding); + static void ToJson(Json::Value& target, DcmDataset& dataset, - unsigned int maxStringLength = 256); - - static void ToJson(Json::Value& target, - const std::string& path, - unsigned int maxStringLength = 256); + DicomToJsonFormat format, + unsigned int maxStringLength); static std::string GetName(const DicomTag& tag); @@ -118,5 +119,18 @@ DcmDataset& dataSet); static ValueRepresentation GetValueRepresentation(const DicomTag& tag); + + static DcmElement* CreateElementForTag(const DicomTag& tag); + + static void FillElementWithString(DcmElement& element, + const DicomTag& tag, + const std::string& utf8alue, // Encoded using UTF-8 + bool interpretBinaryTags, + Encoding dicomEncoding); + + static DcmElement* FromJson(const DicomTag& tag, + const Json::Value& element, // Encoding using UTF-8 + bool interpretBinaryTags, + Encoding dicomEncoding); }; } diff -r 21d31da73374 -r 4aaaecae5803 OrthancServer/Internals/StoreScp.cpp --- a/OrthancServer/Internals/StoreScp.cpp Tue Oct 06 14:44:52 2015 +0200 +++ b/OrthancServer/Internals/StoreScp.cpp Mon Oct 12 14:47:58 2015 +0200 @@ -168,7 +168,8 @@ try { FromDcmtkBridge::Convert(summary, **imageDataSet); - FromDcmtkBridge::ToJson(dicomJson, **imageDataSet); + FromDcmtkBridge::ToJson(dicomJson, **imageDataSet, + DicomToJsonFormat_Full, 256 /* max string length */); if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, **imageDataSet)) { diff -r 21d31da73374 -r 4aaaecae5803 OrthancServer/LuaScripting.cpp --- a/OrthancServer/LuaScripting.cpp Tue Oct 06 14:44:52 2015 +0200 +++ b/OrthancServer/LuaScripting.cpp Mon Oct 12 14:47:58 2015 +0200 @@ -270,10 +270,12 @@ if (operation == "modify") { LOG(INFO) << "Lua script to modify resource " << parameters["Resource"].asString(); - DicomModification modification; - OrthancRestApi::ParseModifyRequest(modification, parameters); + std::auto_ptr modification(new DicomModification); + OrthancRestApi::ParseModifyRequest(*modification, parameters); - std::auto_ptr command(new ModifyInstanceCommand(context_, RequestOrigin_Lua, modification)); + std::auto_ptr command + (new ModifyInstanceCommand(context_, RequestOrigin_Lua, modification.release())); + return command.release(); } diff -r 21d31da73374 -r 4aaaecae5803 OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Tue Oct 06 14:44:52 2015 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Mon Oct 12 14:47:58 2015 +0200 @@ -98,12 +98,13 @@ for (size_t i = 0; i < members.size(); i++) { const std::string& name = members[i]; - std::string value = replacements[name].asString(); + const Json::Value& value = replacements[name]; DicomTag tag = FromDcmtkBridge::ParseTag(name); target.Replace(tag, value); - VLOG(1) << "Replace: " << name << " " << tag << " == " << value << std::endl; + VLOG(1) << "Replace: " << name << " " << tag + << " == " << value.toStyledString() << std::endl; } } @@ -168,7 +169,7 @@ // curl http://localhost:8042/instances/6e67da51-d119d6ae-c5667437-87b9a8a5-0f07c49f/anonymize -X POST -d '{"Replace":{"PatientName":"hello","0010-0020":"world"},"Keep":["StudyDescription", "SeriesDescription"],"KeepPrivateTags": null,"Remove":["Modality"]}' > Anonymized.dcm target.SetupAnonymization(); - std::string patientName = target.GetReplacement(DICOM_TAG_PATIENT_NAME); + std::string patientName = target.GetReplacementAsString(DICOM_TAG_PATIENT_NAME); Json::Value request; if (call.ParseJsonRequest(request) && request.isObject()) @@ -487,7 +488,7 @@ static void InjectTags(ParsedDicomFile& dicom, const Json::Value& tags, - bool interpretBinaryTags) + bool decodeBinaryTags) { if (tags.type() != Json::objectValue) { @@ -499,12 +500,6 @@ for (size_t i = 0; i < members.size(); i++) { const std::string& name = members[i]; - if (tags[name].type() != Json::stringValue) - { - throw OrthancException(ErrorCode_CreateDicomNotString); - } - - std::string value = tags[name].asString(); DicomTag tag = FromDcmtkBridge::ParseTag(name); if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET) @@ -529,16 +524,9 @@ { throw OrthancException(ErrorCode_CreateDicomUseContent); } - else if (interpretBinaryTags && - boost::starts_with(value, "data:application/octet-stream;base64,")) - { - std::string mime, binary; - Toolbox::DecodeDataUriScheme(mime, binary, value); - dicom.Replace(tag, binary); - } else { - dicom.Replace(tag, Toolbox::ConvertFromUtf8(value, dicom.GetEncoding())); + dicom.Replace(tag, tags[name], decodeBinaryTags); } } } @@ -548,7 +536,7 @@ static void CreateSeries(RestApiPostCall& call, ParsedDicomFile& base /* in */, const Json::Value& content, - bool interpretBinaryTags) + bool decodeBinaryTags) { assert(content.isArray()); assert(content.size() > 0); @@ -581,7 +569,7 @@ if (content[i].isMember("Tags")) { - InjectTags(*dicom, content[i]["Tags"], interpretBinaryTags); + InjectTags(*dicom, content[i]["Tags"], decodeBinaryTags); } } @@ -755,7 +743,7 @@ } - bool interpretBinaryTags = true; + bool decodeBinaryTags = true; if (request.isMember("InterpretBinaryTags")) { const Json::Value& v = request["InterpretBinaryTags"]; @@ -764,7 +752,7 @@ throw OrthancException(ErrorCode_BadRequest); } - interpretBinaryTags = v.asBool(); + decodeBinaryTags = v.asBool(); } @@ -794,7 +782,7 @@ } - InjectTags(dicom, request["Tags"], interpretBinaryTags); + InjectTags(dicom, request["Tags"], decodeBinaryTags); // Inject the content (either an image, or a PDF file) @@ -812,7 +800,7 @@ if (content.size() > 0) { // Let's create a series instead of a single instance - CreateSeries(call, dicom, content, interpretBinaryTags); + CreateSeries(call, dicom, content, decodeBinaryTags); return; } } diff -r 21d31da73374 -r 4aaaecae5803 OrthancServer/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Tue Oct 06 14:44:52 2015 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Mon Oct 12 14:47:58 2015 +0200 @@ -479,6 +479,7 @@ { Json::Value operations = Json::arrayValue; + operations.append("compress"); operations.append("compressed-data"); if (info.GetCompressedMD5() != "") @@ -488,6 +489,7 @@ operations.append("compressed-size"); operations.append("data"); + operations.append("is-compressed"); if (info.GetUncompressedMD5() != "") { @@ -495,6 +497,7 @@ } operations.append("size"); + operations.append("uncompress"); if (info.GetCompressedMD5() != "" && info.GetUncompressedMD5() != "") @@ -663,6 +666,31 @@ } + template + static void ChangeAttachmentCompression(RestApiPostCall& call) + { + CheckValidResourceType(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string name = call.GetUriComponent("name", ""); + FileContentType contentType = StringToContentType(name); + + OrthancRestApi::GetContext(call).ChangeAttachmentCompression(publicId, contentType, compression); + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + + + static void IsAttachmentCompressed(RestApiGetCall& call) + { + FileInfo info; + if (GetAttachmentInfo(info, call)) + { + std::string answer = (info.GetCompressionType() == CompressionType_None) ? "0" : "1"; + call.GetOutput().AnswerBuffer(answer, "text/plain"); + } + } + + // Raw access to the DICOM tags of an instance ------------------------------ static void GetRawContent(RestApiGetCall& call) @@ -1125,14 +1153,17 @@ Register("/{resourceType}/{id}/attachments", ListAttachments); Register("/{resourceType}/{id}/attachments/{name}", DeleteAttachment); Register("/{resourceType}/{id}/attachments/{name}", GetAttachmentOperations); + Register("/{resourceType}/{id}/attachments/{name}", UploadAttachment); + Register("/{resourceType}/{id}/attachments/{name}/compress", ChangeAttachmentCompression); Register("/{resourceType}/{id}/attachments/{name}/compressed-data", GetAttachmentData<0>); Register("/{resourceType}/{id}/attachments/{name}/compressed-md5", GetAttachmentCompressedMD5); Register("/{resourceType}/{id}/attachments/{name}/compressed-size", GetAttachmentCompressedSize); Register("/{resourceType}/{id}/attachments/{name}/data", GetAttachmentData<1>); + Register("/{resourceType}/{id}/attachments/{name}/is-compressed", IsAttachmentCompressed); Register("/{resourceType}/{id}/attachments/{name}/md5", GetAttachmentMD5); Register("/{resourceType}/{id}/attachments/{name}/size", GetAttachmentSize); + Register("/{resourceType}/{id}/attachments/{name}/uncompress", ChangeAttachmentCompression); Register("/{resourceType}/{id}/attachments/{name}/verify-md5", VerifyAttachment); - Register("/{resourceType}/{id}/attachments/{name}", UploadAttachment); Register("/tools/lookup", Lookup); Register("/tools/find", Find); diff -r 21d31da73374 -r 4aaaecae5803 OrthancServer/ParsedDicomFile.cpp --- a/OrthancServer/ParsedDicomFile.cpp Tue Oct 06 14:44:52 2015 +0200 +++ b/OrthancServer/ParsedDicomFile.cpp Mon Oct 12 14:47:58 2015 +0200 @@ -137,6 +137,7 @@ #include #include +#include static const char* CONTENT_TYPE_OCTET_STREAM = "application/octet-stream"; @@ -148,7 +149,6 @@ struct ParsedDicomFile::PImpl { std::auto_ptr file_; - Encoding encoding_; }; @@ -173,8 +173,6 @@ } pimpl_->file_->loadAllDataIntoMemory(); pimpl_->file_->transferEnd(); - - pimpl_->encoding_ = FromDcmtkBridge::DetectEncoding(*pimpl_->file_->getDataset()); } @@ -519,284 +517,6 @@ } - - - - static DcmElement* CreateElementForTag(const DicomTag& tag) - { - DcmTag key(tag.GetGroup(), tag.GetElement()); - - switch (key.getEVR()) - { - // http://support.dcmtk.org/docs/dcvr_8h-source.html - - /** - * TODO. - **/ - - case EVR_OB: // other byte - case EVR_OF: // other float - 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. - * http://support.dcmtk.org/docs/classDcmByteString.html - **/ - - case EVR_AS: // age string - return new DcmAgeString(key); - - case EVR_AE: // application entity title - return new DcmApplicationEntity(key); - - case EVR_CS: // code string - return new DcmCodeString(key); - - case EVR_DA: // date string - return new DcmDate(key); - - case EVR_DT: // date time string - return new DcmDateTime(key); - - case EVR_DS: // decimal string - return new DcmDecimalString(key); - - case EVR_IS: // integer string - return new DcmIntegerString(key); - - case EVR_TM: // time string - return new DcmTime(key); - - case EVR_UI: // unique identifier - return new DcmUniqueIdentifier(key); - - case EVR_ST: // short text - return new DcmShortText(key); - - case EVR_LO: // long string - return new DcmLongString(key); - - case EVR_LT: // long text - return new DcmLongText(key); - - case EVR_UT: // unlimited text - return new DcmUnlimitedText(key); - - case EVR_SH: // short string - return new DcmShortString(key); - - case EVR_PN: // person name - return new DcmPersonName(key); - - - /** - * Numerical types - **/ - - case EVR_SL: // signed long - return new DcmSignedLong(key); - - case EVR_SS: // signed short - return new DcmSignedShort(key); - - case EVR_UL: // unsigned long - return new DcmUnsignedLong(key); - - case EVR_US: // unsigned short - return new DcmUnsignedShort(key); - - case EVR_FL: // float single-precision - return new DcmFloatingPointSingle(key); - - case EVR_FD: // float double-precision - return new DcmFloatingPointDouble(key); - - - /** - * Sequence types, should never occur at this point. - **/ - - case EVR_SQ: // sequence of items - throw OrthancException(ErrorCode_ParameterOutOfRange); - - - /** - * 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; - } - - throw OrthancException(ErrorCode_InternalError); - } - - - - static void FillElementWithString(DcmElement& element, - const DicomTag& tag, - const std::string& value) - { - DcmTag key(tag.GetGroup(), tag.GetElement()); - bool ok = false; - - try - { - switch (key.getEVR()) - { - // http://support.dcmtk.org/docs/dcvr_8h-source.html - - /** - * TODO. - **/ - - case EVR_OB: // other byte - case EVR_OF: // other float - 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 - { - ok = element.putString(value.c_str()).good(); - break; - } - - - /** - * Numerical types - **/ - - case EVR_SL: // signed long - { - ok = element.putSint32(boost::lexical_cast(value)).good(); - break; - } - - case EVR_SS: // signed short - { - ok = element.putSint16(boost::lexical_cast(value)).good(); - break; - } - - case EVR_UL: // unsigned long - { - ok = element.putUint32(boost::lexical_cast(value)).good(); - break; - } - - case EVR_US: // unsigned short - { - ok = element.putUint16(boost::lexical_cast(value)).good(); - break; - } - - case EVR_FL: // float single-precision - { - ok = element.putFloat32(boost::lexical_cast(value)).good(); - break; - } - - case EVR_FD: // float double-precision - { - ok = element.putFloat64(boost::lexical_cast(value)).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) - { - throw OrthancException(ErrorCode_InternalError); - } - } - - void ParsedDicomFile::Remove(const DicomTag& tag) { DcmTagKey key(tag.GetGroup(), tag.GetElement()); @@ -823,7 +543,7 @@ DcmTag tag(element->getTag()); // Is this a private tag? - if (FromDcmtkBridge::IsPrivateTag(tag)) + if (tag.isPrivate()) { bool remove = true; @@ -857,54 +577,40 @@ } - - - void ParsedDicomFile::Insert(const DicomTag& tag, - const std::string& value) + static void InsertInternal(DcmDataset& dicom, + DcmElement* element) { - OFCondition cond; - - if (FromDcmtkBridge::IsPrivateTag(tag) || - FromDcmtkBridge::IsUnknownTag(tag)) - { - // This is a private tag - // http://support.dcmtk.org/redmine/projects/dcmtk/wiki/howto_addprivatedata - - DcmTag key(tag.GetGroup(), tag.GetElement(), EVR_OB); - cond = pimpl_->file_->getDataset()->putAndInsertUint8Array - (key, (const Uint8*) value.c_str(), value.size(), false); - } - else - { - std::auto_ptr element(CreateElementForTag(tag)); - FillElementWithString(*element, tag, value); - - cond = pimpl_->file_->getDataset()->insert(element.release(), false, false); - } - + OFCondition cond = dicom.insert(element, false, false); if (!cond.good()) { // This field already exists + delete element; throw OrthancException(ErrorCode_InternalError); } } - void ParsedDicomFile::Replace(const DicomTag& tag, - const std::string& value, - DicomReplaceMode mode) + void ParsedDicomFile::Insert(const DicomTag& tag, + const Json::Value& value, + bool decodeBinaryTags) { - DcmTagKey key(tag.GetGroup(), tag.GetElement()); - DcmElement* element = NULL; + std::auto_ptr element(FromDcmtkBridge::FromJson(tag, value, decodeBinaryTags, GetEncoding())); + InsertInternal(*pimpl_->file_->getDataset(), element.release()); + } + - if (!pimpl_->file_->getDataset()->findAndGetElement(key, element).good() || - element == NULL) + static void ReplaceInternal(DcmDataset& dicom, + std::auto_ptr& element, + DicomReplaceMode mode) + { + const DcmTagKey& tag = element->getTag(); + + if (!dicom.findAndDeleteElement(tag).good()) { // This field does not exist, act wrt. the specified "mode" switch (mode) { case DicomReplaceMode_InsertIfAbsent: - Insert(tag, value); break; case DicomReplaceMode_ThrowIfAbsent: @@ -914,23 +620,43 @@ return; } } + + // Either the tag was not existing, or the replace mode was set to + // "InsertIfAbsent" + InsertInternal(dicom, element.release()); + } + + + void ParsedDicomFile::UpdateStorageUid(const DicomTag& tag, + const std::string& utf8Value, + bool decodeBinaryTags) + { + if (tag != DICOM_TAG_SOP_CLASS_UID && + tag != DICOM_TAG_SOP_INSTANCE_UID) + { + return; + } + + std::string binary; + const std::string* decoded = &utf8Value; + + if (decodeBinaryTags && + boost::starts_with(utf8Value, "data:application/octet-stream;base64,")) + { + std::string mime; + Toolbox::DecodeDataUriScheme(mime, binary, utf8Value); + decoded = &binary; + } else { - if (FromDcmtkBridge::IsPrivateTag(tag) || - FromDcmtkBridge::IsUnknownTag(tag)) + Encoding encoding = GetEncoding(); + if (GetEncoding() != Encoding_Utf8) { - if (!element->putUint8Array((const Uint8*) value.c_str(), value.size()).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - } - else - { - FillElementWithString(*element, tag, value); + 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 @@ -942,12 +668,44 @@ if (tag == DICOM_TAG_SOP_CLASS_UID) { - Replace(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, value, DicomReplaceMode_InsertIfAbsent); + Replace(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, *decoded, DicomReplaceMode_InsertIfAbsent); } if (tag == DICOM_TAG_SOP_INSTANCE_UID) { - Replace(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, value, DicomReplaceMode_InsertIfAbsent); + Replace(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, *decoded, DicomReplaceMode_InsertIfAbsent); + } + } + + + void ParsedDicomFile::Replace(const DicomTag& tag, + const std::string& utf8Value, + DicomReplaceMode mode) + { + std::auto_ptr element(FromDcmtkBridge::CreateElementForTag(tag)); + FromDcmtkBridge::FillElementWithString(*element, tag, utf8Value, false, GetEncoding()); + ReplaceInternal(*pimpl_->file_->getDataset(), element, mode); + UpdateStorageUid(tag, utf8Value, false); + } + + + void ParsedDicomFile::Replace(const DicomTag& tag, + const Json::Value& value, + bool decodeBinaryTags, + DicomReplaceMode mode) + { + std::auto_ptr element(FromDcmtkBridge::FromJson(tag, value, decodeBinaryTags, GetEncoding())); + ReplaceInternal(*pimpl_->file_->getDataset(), element, mode); + + 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(), decodeBinaryTags); } } @@ -1005,7 +763,7 @@ return false; } - std::auto_ptr v(FromDcmtkBridge::ConvertLeafElement(*element, pimpl_->encoding_)); + std::auto_ptr v(FromDcmtkBridge::ConvertLeafElement(*element, GetEncoding())); if (v.get() == NULL) { @@ -1085,7 +843,6 @@ ParsedDicomFile::ParsedDicomFile() : pimpl_(new PImpl) { pimpl_->file_.reset(new DcmFileFormat); - pimpl_->encoding_ = Encoding_Ascii; Replace(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient)); Replace(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study)); Replace(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series)); @@ -1115,7 +872,6 @@ pimpl_(new PImpl) { pimpl_->file_.reset(dynamic_cast(other.pimpl_->file_->clone())); - pimpl_->encoding_ = other.pimpl_->encoding_; // Create a new instance-level identifier Replace(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance)); @@ -1156,7 +912,7 @@ } else { - LOG(ERROR) << "Unsupported MIME type for the content of a new DICOM file"; + LOG(ERROR) << "Unsupported MIME type for the content of a new DICOM file: " << mime; throw OrthancException(ErrorCode_NotImplemented); } } @@ -1345,7 +1101,7 @@ Encoding ParsedDicomFile::GetEncoding() const { - return pimpl_->encoding_; + return FromDcmtkBridge::DetectEncoding(*pimpl_->file_->getDataset()); } @@ -1358,24 +1114,15 @@ return; } - pimpl_->encoding_ = encoding; - std::string s = GetDicomSpecificCharacterSet(encoding); Replace(DICOM_TAG_SPECIFIC_CHARACTER_SET, s, DicomReplaceMode_InsertIfAbsent); } - void ParsedDicomFile::ToJson(Json::Value& target, bool simplify) + void ParsedDicomFile::ToJson(Json::Value& target, + DicomToJsonFormat format, + unsigned int maxStringLength) { - if (simplify) - { - Json::Value tmp; - FromDcmtkBridge::ToJson(tmp, *pimpl_->file_->getDataset()); - Toolbox::SimplifyTags(target, tmp); - } - else - { - FromDcmtkBridge::ToJson(target, *pimpl_->file_->getDataset()); - } + FromDcmtkBridge::ToJson(target, *pimpl_->file_->getDataset(), format, maxStringLength); } diff -r 21d31da73374 -r 4aaaecae5803 OrthancServer/ParsedDicomFile.h --- a/OrthancServer/ParsedDicomFile.h Tue Oct 06 14:44:52 2015 +0200 +++ b/OrthancServer/ParsedDicomFile.h Mon Oct 12 14:47:58 2015 +0200 @@ -53,6 +53,10 @@ void RemovePrivateTagsInternal(const std::set* toKeep); + void UpdateStorageUid(const DicomTag& tag, + const std::string& value, + bool decodeBinaryTags); + public: ParsedDicomFile(); // Create a minimal DICOM instance @@ -74,13 +78,19 @@ void Remove(const DicomTag& tag); - void Insert(const DicomTag& tag, - const std::string& value); + void Replace(const DicomTag& tag, + const std::string& utf8Value, + DicomReplaceMode mode = DicomReplaceMode_InsertIfAbsent); void Replace(const DicomTag& tag, - const std::string& value, + const Json::Value& value, // Assumed to be encoded with UTF-8 + bool decodeBinaryTags, DicomReplaceMode mode = DicomReplaceMode_InsertIfAbsent); + void Insert(const DicomTag& tag, + const Json::Value& value, // Assumed to be encoded with UTF-8 + bool decodeBinaryTags); + void RemovePrivateTags() { RemovePrivateTagsInternal(NULL); @@ -123,7 +133,8 @@ void SetEncoding(Encoding encoding); void ToJson(Json::Value& target, - bool simplify); + DicomToJsonFormat format, + unsigned int maxStringLength); bool HasTag(const DicomTag& tag) const; diff -r 21d31da73374 -r 4aaaecae5803 OrthancServer/Scheduler/ModifyInstanceCommand.cpp --- a/OrthancServer/Scheduler/ModifyInstanceCommand.cpp Tue Oct 06 14:44:52 2015 +0200 +++ b/OrthancServer/Scheduler/ModifyInstanceCommand.cpp Mon Oct 12 14:47:58 2015 +0200 @@ -39,28 +39,28 @@ { ModifyInstanceCommand::ModifyInstanceCommand(ServerContext& context, RequestOrigin origin, - const DicomModification& modification) : + DicomModification* modification) : context_(context), origin_(origin), modification_(modification) { - modification_.SetAllowManualIdentifiers(true); + modification_->SetAllowManualIdentifiers(true); - if (modification_.IsReplaced(DICOM_TAG_PATIENT_ID)) + if (modification_->IsReplaced(DICOM_TAG_PATIENT_ID)) { - modification_.SetLevel(ResourceType_Patient); + modification_->SetLevel(ResourceType_Patient); } - else if (modification_.IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) + else if (modification_->IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) { - modification_.SetLevel(ResourceType_Study); + modification_->SetLevel(ResourceType_Study); } - else if (modification_.IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) + else if (modification_->IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) { - modification_.SetLevel(ResourceType_Series); + modification_->SetLevel(ResourceType_Series); } else { - modification_.SetLevel(ResourceType_Instance); + modification_->SetLevel(ResourceType_Instance); } if (origin_ != RequestOrigin_Lua) @@ -71,6 +71,15 @@ } + ModifyInstanceCommand::~ModifyInstanceCommand() + { + if (modification_) + { + delete modification_; + } + } + + bool ModifyInstanceCommand::Apply(ListOfStrings& outputs, const ListOfStrings& inputs) { @@ -88,7 +97,7 @@ modified.reset(lock.GetDicom().Clone()); } - modification_.Apply(*modified); + modification_->Apply(*modified); DicomInstanceToStore toStore; assert(origin_ == RequestOrigin_Lua); diff -r 21d31da73374 -r 4aaaecae5803 OrthancServer/Scheduler/ModifyInstanceCommand.h --- a/OrthancServer/Scheduler/ModifyInstanceCommand.h Tue Oct 06 14:44:52 2015 +0200 +++ b/OrthancServer/Scheduler/ModifyInstanceCommand.h Mon Oct 12 14:47:58 2015 +0200 @@ -43,16 +43,18 @@ private: ServerContext& context_; RequestOrigin origin_; - DicomModification modification_; + DicomModification* modification_; public: ModifyInstanceCommand(ServerContext& context, RequestOrigin origin, - const DicomModification& modification); + DicomModification* modification); // takes the ownership + + virtual ~ModifyInstanceCommand(); const DicomModification& GetModification() const { - return modification_; + return *modification_; } virtual bool Apply(ListOfStrings& outputs, diff -r 21d31da73374 -r 4aaaecae5803 OrthancServer/ServerContext.cpp --- a/OrthancServer/ServerContext.cpp Tue Oct 06 14:44:52 2015 +0200 +++ b/OrthancServer/ServerContext.cpp Mon Oct 12 14:47:58 2015 +0200 @@ -307,15 +307,14 @@ } - void ServerContext::AnswerAttachment(RestApiOutput& output, - const std::string& instancePublicId, + const std::string& resourceId, FileContentType content) { FileInfo attachment; - if (!index_.LookupAttachment(attachment, instancePublicId, content)) + if (!index_.LookupAttachment(attachment, resourceId, content)) { - throw OrthancException(ErrorCode_InternalError); + throw OrthancException(ErrorCode_UnknownResource); } StorageAccessor accessor(area_); @@ -323,6 +322,52 @@ } + void ServerContext::ChangeAttachmentCompression(const std::string& resourceId, + FileContentType attachmentType, + CompressionType compression) + { + LOG(INFO) << "Changing compression type for attachment " + << EnumerationToString(attachmentType) + << " of resource " << resourceId << " to " + << compression; + + FileInfo attachment; + if (!index_.LookupAttachment(attachment, resourceId, attachmentType)) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + if (attachment.GetCompressionType() == compression) + { + // Nothing to do + return; + } + + std::string content; + + StorageAccessor accessor(area_); + accessor.Read(content, attachment); + + FileInfo modified = accessor.Write(content.empty() ? NULL : content.c_str(), + content.size(), attachmentType, compression, storeMD5_); + + try + { + StoreStatus status = index_.AddAttachment(modified, resourceId); + if (status != StoreStatus_Success) + { + accessor.Remove(modified); + throw OrthancException(ErrorCode_Database); + } + } + catch (OrthancException&) + { + accessor.Remove(modified); + throw; + } + } + + void ServerContext::ReadJson(Json::Value& result, const std::string& instancePublicId) { diff -r 21d31da73374 -r 4aaaecae5803 OrthancServer/ServerContext.h --- a/OrthancServer/ServerContext.h Tue Oct 06 14:44:52 2015 +0200 +++ b/OrthancServer/ServerContext.h Mon Oct 12 14:47:58 2015 +0200 @@ -184,9 +184,13 @@ DicomInstanceToStore& dicom); void AnswerAttachment(RestApiOutput& output, - const std::string& instancePublicId, + const std::string& resourceId, FileContentType content); + void ChangeAttachmentCompression(const std::string& resourceId, + FileContentType attachmentType, + CompressionType compression); + void ReadJson(Json::Value& result, const std::string& instancePublicId); diff -r 21d31da73374 -r 4aaaecae5803 OrthancServer/ServerEnumerations.h --- a/OrthancServer/ServerEnumerations.h Tue Oct 06 14:44:52 2015 +0200 +++ b/OrthancServer/ServerEnumerations.h Mon Oct 12 14:47:58 2015 +0200 @@ -100,6 +100,13 @@ ValueRepresentation_Time }; + enum DicomToJsonFormat + { + DicomToJsonFormat_Full, + DicomToJsonFormat_Short, + DicomToJsonFormat_Simple + }; + /** * WARNING: Do not change the explicit values in the enumerations diff -r 21d31da73374 -r 4aaaecae5803 Resources/Samples/Lua/ModifyInstanceWithSequence.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/Lua/ModifyInstanceWithSequence.lua Mon Oct 12 14:47:58 2015 +0200 @@ -0,0 +1,28 @@ +-- Answer to: +-- https://groups.google.com/d/msg/orthanc-users/0ymHe1cDBCQ/YfD0NoOTn0wJ +-- Applicable starting with Orthanc 0.9.5 + +function OnStoredInstance(instanceId, tags, metadata, origin) + -- Do not modify twice the same file + if origin['RequestOrigin'] ~= 'Lua' then + local replace = {} + replace['0010,1002'] = {} + replace['0010,1002'][1] = {} + replace['0010,1002'][1]['PatientID'] = 'Hello' + replace['0010,1002'][2] = {} + replace['0010,1002'][2]['PatientID'] = 'World' + + local request = {} + request['Replace'] = replace + + -- Create the modified instance + local modified = RestApiPost('/instances/' .. instanceId .. '/modify', + DumpJson(request)) + + -- Upload the modified instance to the Orthanc store + RestApiPost('/instances/', modified) + + -- Delete the original instance + RestApiDelete('/instances/' .. instanceId) + end +end diff -r 21d31da73374 -r 4aaaecae5803 THANKS --- a/THANKS Tue Oct 06 14:44:52 2015 +0200 +++ b/THANKS Mon Oct 12 14:47:58 2015 +0200 @@ -28,7 +28,6 @@ * Vincent Kersten , for DICOMDIR in the GUI. * Emsy Chan , for various contributions and sample DICOM files. -* Mikhail , for FreeBSD support. Thanks also to all the contributors active in our Google Group: @@ -49,6 +48,12 @@ * Mario Ceresa , for help about packaging. +FreeBSD +------- + +* Mikhail , for FreeBSD packaging. + + Artwork ------- diff -r 21d31da73374 -r 4aaaecae5803 UnitTestsSources/FromDcmtkTests.cpp --- a/UnitTestsSources/FromDcmtkTests.cpp Tue Oct 06 14:44:52 2015 +0200 +++ b/UnitTestsSources/FromDcmtkTests.cpp Mon Oct 12 14:47:58 2015 +0200 @@ -36,6 +36,7 @@ #include "../OrthancServer/FromDcmtkBridge.h" #include "../OrthancServer/OrthancInitialization.h" #include "../OrthancServer/DicomModification.h" +#include "../OrthancServer/ServerToolbox.h" #include "../Core/OrthancException.h" #include "../Core/Images/ImageBuffer.h" #include "../Core/Images/PngReader.h" @@ -43,6 +44,8 @@ #include "../Core/Uuid.h" #include "../Resources/EncodingTests.h" +#include + using namespace Orthanc; TEST(DicomFormat, Tag) @@ -102,7 +105,7 @@ ParsedDicomFile o; o.Replace(DICOM_TAG_PATIENT_NAME, "coucou"); ASSERT_FALSE(o.GetTagValue(s, privateTag)); - o.Insert(privateTag, "private tag"); + o.Insert(privateTag, "private tag", false); ASSERT_TRUE(o.GetTagValue(s, privateTag)); ASSERT_STREQ("private tag", s.c_str()); @@ -204,7 +207,7 @@ std::string source(testEncodingsEncoded[i]); std::string expected(testEncodingsExpected[i]); std::string s = Toolbox::ConvertToUtf8(source, testEncodings[i]); - std::cout << EnumerationToString(testEncodings[i]) << std::endl; + //std::cout << EnumerationToString(testEncodings[i]) << std::endl; EXPECT_EQ(expected, s); } } @@ -259,13 +262,15 @@ { for (unsigned int i = 0; i < testEncodingsCount; i++) { - std::cout << EnumerationToString(testEncodings[i]) << std::endl; + //std::cout << EnumerationToString(testEncodings[i]) << std::endl; std::string dicom; { ParsedDicomFile f; f.SetEncoding(testEncodings[i]); - f.Insert(DICOM_TAG_PATIENT_NAME, testEncodingsEncoded[i]); + + std::string s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i]); + f.Insert(DICOM_TAG_PATIENT_NAME, s, false); f.SaveToMemoryBuffer(dicom); } @@ -299,3 +304,219 @@ ASSERT_EQ(ValueRepresentation_Other, FromDcmtkBridge::GetValueRepresentation(DICOM_TAG_PATIENT_ID)); } + + + +static const DicomTag REFERENCED_STUDY_SEQUENCE(0x0008, 0x1110); +static const DicomTag REFERENCED_PATIENT_SEQUENCE(0x0008, 0x1120); + +static void CreateSampleJson(Json::Value& a) +{ + { + Json::Value b = Json::objectValue; + b["PatientName"] = "Hello"; + b["PatientID"] = "World"; + b["StudyDescription"] = "Toto"; + a.append(b); + } + + { + Json::Value b = Json::objectValue; + b["PatientName"] = "data:application/octet-stream;base64,SGVsbG8y"; // echo -n "Hello2" | base64 + b["PatientID"] = "World2"; + a.append(b); + } +} + + +TEST(FromDcmtkBridge, FromJson) +{ + std::auto_ptr element; + + { + Json::Value a; + a = "Hello"; + element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false, Encoding_Utf8)); + + Json::Value b; + FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, 0, Encoding_Ascii); + ASSERT_EQ("Hello", b["0010,0010"].asString()); + } + + { + Json::Value a; + a = "Hello"; + // Cannot assign a string to a sequence + ASSERT_THROW(element.reset(FromDcmtkBridge::FromJson(REFERENCED_STUDY_SEQUENCE, a, false, Encoding_Utf8)), OrthancException); + } + + { + Json::Value a = Json::arrayValue; + a.append("Hello"); + // Cannot assign an array to a string + ASSERT_THROW(element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false, Encoding_Utf8)), OrthancException); + } + + { + Json::Value a; + a = "data:application/octet-stream;base64,SGVsbG8="; // echo -n "Hello" | base64 + element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, true, Encoding_Utf8)); + + Json::Value b; + FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, 0, Encoding_Ascii); + ASSERT_EQ("Hello", b["0010,0010"].asString()); + } + + { + Json::Value a = Json::arrayValue; + CreateSampleJson(a); + element.reset(FromDcmtkBridge::FromJson(REFERENCED_STUDY_SEQUENCE, a, true, Encoding_Utf8)); + + { + Json::Value b; + FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, 0, Encoding_Ascii); + ASSERT_EQ(Json::arrayValue, b["0008,1110"].type()); + ASSERT_EQ(2, b["0008,1110"].size()); + + Json::Value::ArrayIndex i = (b["0008,1110"][0]["0010,0010"].asString() == "Hello") ? 0 : 1; + + ASSERT_EQ(3, b["0008,1110"][i].size()); + ASSERT_EQ(2, b["0008,1110"][1 - i].size()); + ASSERT_EQ(b["0008,1110"][i]["0010,0010"].asString(), "Hello"); + ASSERT_EQ(b["0008,1110"][i]["0010,0020"].asString(), "World"); + ASSERT_EQ(b["0008,1110"][i]["0008,1030"].asString(), "Toto"); + ASSERT_EQ(b["0008,1110"][1 - i]["0010,0010"].asString(), "Hello2"); + ASSERT_EQ(b["0008,1110"][1 - i]["0010,0020"].asString(), "World2"); + } + + { + Json::Value b; + FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Full, 0, Encoding_Ascii); + + Json::Value c; + Toolbox::SimplifyTags(c, b); + + a[1]["PatientName"] = "Hello2"; // To remove the Data URI Scheme encoding + ASSERT_EQ(0, c["ReferencedStudySequence"].compare(a)); + } + } +} + + + +TEST(ParsedDicomFile, InsertReplaceStrings) +{ + ParsedDicomFile f; + + f.Insert(DICOM_TAG_PATIENT_NAME, "World", false); + ASSERT_THROW(f.Insert(DICOM_TAG_PATIENT_ID, "Hello", false), OrthancException); // Already existing tag + f.Replace(DICOM_TAG_SOP_INSTANCE_UID, "Toto"); // (*) + f.Replace(DICOM_TAG_SOP_CLASS_UID, "Tata"); // (**) + + std::string s; + + ASSERT_THROW(f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession", DicomReplaceMode_ThrowIfAbsent), OrthancException); + f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession", DicomReplaceMode_IgnoreIfAbsent); + ASSERT_FALSE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER)); + f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession", DicomReplaceMode_InsertIfAbsent); + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER)); + ASSERT_EQ(s, "Accession"); + f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession2", DicomReplaceMode_IgnoreIfAbsent); + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER)); + ASSERT_EQ(s, "Accession2"); + f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession3", DicomReplaceMode_ThrowIfAbsent); + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER)); + ASSERT_EQ(s, "Accession3"); + + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_PATIENT_NAME)); + ASSERT_EQ(s, "World"); + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_INSTANCE_UID)); + ASSERT_EQ(s, "Toto"); + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID)); // Implicitly modified by (*) + ASSERT_EQ(s, "Toto"); + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_CLASS_UID)); + ASSERT_EQ(s, "Tata"); + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID)); // Implicitly modified by (**) + ASSERT_EQ(s, "Tata"); +} + + + + +TEST(ParsedDicomFile, InsertReplaceJson) +{ + ParsedDicomFile f; + + Json::Value a; + CreateSampleJson(a); + + ASSERT_FALSE(f.HasTag(REFERENCED_STUDY_SEQUENCE)); + f.Remove(REFERENCED_STUDY_SEQUENCE); // No effect + f.Insert(REFERENCED_STUDY_SEQUENCE, a, true); + ASSERT_TRUE(f.HasTag(REFERENCED_STUDY_SEQUENCE)); + ASSERT_THROW(f.Insert(REFERENCED_STUDY_SEQUENCE, a, true), OrthancException); + f.Remove(REFERENCED_STUDY_SEQUENCE); + ASSERT_FALSE(f.HasTag(REFERENCED_STUDY_SEQUENCE)); + f.Insert(REFERENCED_STUDY_SEQUENCE, a, true); + ASSERT_TRUE(f.HasTag(REFERENCED_STUDY_SEQUENCE)); + + ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE)); + ASSERT_THROW(f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_ThrowIfAbsent), OrthancException); + ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE)); + f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_IgnoreIfAbsent); + ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE)); + f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_InsertIfAbsent); + ASSERT_TRUE(f.HasTag(REFERENCED_PATIENT_SEQUENCE)); + + { + Json::Value b; + f.ToJson(b, DicomToJsonFormat_Full, 0); + + Json::Value c; + Toolbox::SimplifyTags(c, b); + + ASSERT_EQ(0, c["ReferencedPatientSequence"].compare(a)); + ASSERT_NE(0, c["ReferencedStudySequence"].compare(a)); // Because Data URI Scheme decoding was enabled + } + + a = "data:application/octet-stream;base64,VGF0YQ=="; // echo -n "Tata" | base64 + f.Replace(DICOM_TAG_SOP_INSTANCE_UID, a, false); // (*) + f.Replace(DICOM_TAG_SOP_CLASS_UID, a, true); // (**) + + std::string s; + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_INSTANCE_UID)); + ASSERT_EQ(s, a.asString()); + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID)); // Implicitly modified by (*) + ASSERT_EQ(s, a.asString()); + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_CLASS_UID)); + ASSERT_EQ(s, "Tata"); + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID)); // Implicitly modified by (**) + ASSERT_EQ(s, "Tata"); +} + + +TEST(ParsedDicomFile, JsonEncoding) +{ + ParsedDicomFile f; + + for (unsigned int i = 0; i < testEncodingsCount; i++) + { + if (testEncodings[i] != Encoding_Windows1251) + { + //std::cout << EnumerationToString(testEncodings[i]) << std::endl; + f.SetEncoding(testEncodings[i]); + + if (testEncodings[i] != Encoding_Ascii) + { + ASSERT_EQ(testEncodings[i], f.GetEncoding()); + } + + Json::Value s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i]); + f.Replace(DICOM_TAG_PATIENT_NAME, s, false); + + Json::Value v; + f.ToJson(v, DicomToJsonFormat_Simple, 0); + ASSERT_EQ(v["PatientName"].asString(), std::string(testEncodingsExpected[i])); + } + } +}