# HG changeset patch # User Sebastien Jodogne # Date 1582663449 -3600 # Node ID 4922bdd046ddb952757338be2f38e13abedbde40 # Parent a9ce35d67c3cff30deff7476d415a174a5283f4d Fix issue #140 (Modifying private tags with REST API changes VR from LO to UN) - DANGEROUS COMMIT diff -r a9ce35d67c3c -r 4922bdd046dd Core/DicomNetworking/Internals/CommandDispatcher.cpp --- a/Core/DicomNetworking/Internals/CommandDispatcher.cpp Tue Feb 25 13:57:43 2020 +0100 +++ b/Core/DicomNetworking/Internals/CommandDispatcher.cpp Tue Feb 25 21:44:09 2020 +0100 @@ -430,6 +430,7 @@ transferSyntaxes.push_back(UID_MPEG2MainProfileAtHighLevelTransferSyntax); } +#if DCMTK_VERSION_NUMBER >= 361 // New in Orthanc 1.6.0 if (!server.HasApplicationEntityFilter() || server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Mpeg4)) @@ -440,6 +441,7 @@ transferSyntaxes.push_back(UID_MPEG4HighProfileLevel4_2_For3DVideoTransferSyntax); transferSyntaxes.push_back(UID_MPEG4StereoHighProfileLevel4_2TransferSyntax); } +#endif if (!server.HasApplicationEntityFilter() || server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Rle)) diff -r a9ce35d67c3c -r 4922bdd046dd Core/DicomParsing/DicomModification.cpp --- a/Core/DicomParsing/DicomModification.cpp Tue Feb 25 13:57:43 2020 +0100 +++ b/Core/DicomParsing/DicomModification.cpp Tue Feb 25 21:44:09 2020 +0100 @@ -347,7 +347,7 @@ dicom.Replace(*tag, mapped, false /* don't try and decode data URI scheme for UIDs */, - DicomReplaceMode_InsertIfAbsent); + DicomReplaceMode_InsertIfAbsent, privateCreator_); } @@ -359,6 +359,7 @@ keepSeriesInstanceUid_(false), updateReferencedRelationships_(true), isAnonymization_(false), + //privateCreator_("PrivateCreator"), identifierGenerator_(NULL) { } @@ -1067,7 +1068,8 @@ for (Replacements::const_iterator it = replacements_.begin(); it != replacements_.end(); ++it) { - toModify.Replace(it->first, *it->second, true /* decode data URI scheme */, DicomReplaceMode_InsertIfAbsent); + toModify.Replace(it->first, *it->second, true /* decode data URI scheme */, + DicomReplaceMode_InsertIfAbsent, privateCreator_); } // (6) Update the DICOM identifiers @@ -1262,6 +1264,12 @@ { ParseListOfTags(*this, request["Keep"], TagOperation_Keep, force); } + + // New in Orthanc 1.6.0 + if (request.isMember("PrivateCreator")) + { + privateCreator_ = SerializationToolbox::ReadString(request, "PrivateCreator"); + } } @@ -1336,6 +1344,7 @@ static const char* MAP_STUDIES = "MapStudies"; static const char* MAP_SERIES = "MapSeries"; static const char* MAP_INSTANCES = "MapInstances"; + static const char* PRIVATE_CREATOR = "PrivateCreator"; // New in Orthanc 1.6.0 void DicomModification::Serialize(Json::Value& value) const { @@ -1353,6 +1362,7 @@ value[KEEP_SERIES_INSTANCE_UID] = keepSeriesInstanceUid_; value[UPDATE_REFERENCED_RELATIONSHIPS] = updateReferencedRelationships_; value[IS_ANONYMIZATION] = isAnonymization_; + value[PRIVATE_CREATOR] = privateCreator_; SerializationToolbox::WriteSetOfTags(value, removals_, REMOVALS); SerializationToolbox::WriteSetOfTags(value, clearings_, CLEARINGS); @@ -1451,6 +1461,11 @@ (serialized, UPDATE_REFERENCED_RELATIONSHIPS); isAnonymization_ = SerializationToolbox::ReadBoolean(serialized, IS_ANONYMIZATION); + if (serialized.isMember(PRIVATE_CREATOR)) + { + privateCreator_ = SerializationToolbox::ReadString(serialized, PRIVATE_CREATOR); + } + SerializationToolbox::ReadSetOfTags(removals_, serialized, REMOVALS); SerializationToolbox::ReadSetOfTags(clearings_, serialized, CLEARINGS); SerializationToolbox::ReadSetOfTags(privateTagsToKeep_, serialized, PRIVATE_TAGS_TO_KEEP); diff -r a9ce35d67c3c -r 4922bdd046dd Core/DicomParsing/DicomModification.h --- a/Core/DicomParsing/DicomModification.h Tue Feb 25 13:57:43 2020 +0100 +++ b/Core/DicomParsing/DicomModification.h Tue Feb 25 21:44:09 2020 +0100 @@ -86,6 +86,7 @@ bool updateReferencedRelationships_; bool isAnonymization_; DicomMap currentSource_; + std::string privateCreator_; IDicomIdentifierGenerator* identifierGenerator_; @@ -185,5 +186,15 @@ } void Serialize(Json::Value& value) const; + + void SetPrivateCreator(std::string& privateCreator) + { + privateCreator_ = privateCreator; + } + + const std::string& GetPrivateCreator() + { + return privateCreator_; + } }; } diff -r a9ce35d67c3c -r 4922bdd046dd Core/DicomParsing/FromDcmtkBridge.cpp --- a/Core/DicomParsing/FromDcmtkBridge.cpp Tue Feb 25 13:57:43 2020 +0100 +++ b/Core/DicomParsing/FromDcmtkBridge.cpp Tue Feb 25 21:44:09 2020 +0100 @@ -115,6 +115,16 @@ 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, EmbeddedResources::FileResourceId resource) @@ -938,18 +948,13 @@ if (!(flags & DicomToJsonFlags_IncludeUnknownTags)) { DictionaryLocker locker; - if (locker->findEntry(element->getTag(), NULL) == NULL) + if (locker->findEntry(element->getTag(), element->getTag().getPrivateCreator()) == NULL) { continue; } } - DcmEVR evr = element->getTag().getEVR(); - if (evr == EVR_OB || - evr == EVR_OF || - evr == EVR_OW || - evr == EVR_UN || - evr == EVR_ox) + if (IsBinaryTag(element->getTag())) { // This is a binary tag if ((tag == DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludePixelData)) || @@ -1371,189 +1376,49 @@ } - static bool IsBinaryTag(const DcmTag& key) + DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag, + const std::string& privateCreator) { - return (key.isUnknownVR() || -#if DCMTK_VERSION_NUMBER >= 361 - key.getEVR() == EVR_OD || -#endif - -#if DCMTK_VERSION_NUMBER >= 362 - key.getEVR() == EVR_OL || -#endif - key.getEVR() == EVR_OB || - key.getEVR() == EVR_OF || - key.getEVR() == EVR_OW || - key.getEVR() == EVR_UN || - key.getEVR() == EVR_ox); - } - - - DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag) - { - DcmTag key(tag.GetGroup(), tag.GetElement()); - - if (tag.IsPrivate() || - IsBinaryTag(key)) + if (tag.IsPrivate() && + privateCreator.empty()) { - return new DcmOtherByteOtherWord(key); + // 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(); } - - switch (key.getEVR()) - { - // http://support.dcmtk.org/docs/dcvr_8h-source.html - - /** - * Binary types, handled above - **/ #if DCMTK_VERSION_NUMBER >= 361 - case EVR_OD: -#endif - -#if DCMTK_VERSION_NUMBER >= 362 - case EVR_OL: -#endif - - 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 - throw OrthancException(ErrorCode_InternalError); - - - /** - * 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); - -#if DCMTK_VERSION_NUMBER >= 361 - case EVR_UC: // unlimited characters - return new DcmUnlimitedCharacters(key); -#endif - -#if DCMTK_VERSION_NUMBER >= 361 - case EVR_UR: // URI/URL - return new DcmUniversalResourceIdentifierOrLocator(key); -#endif - - - /** - * 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); - - - /** - * TODO - **/ - - case EVR_AT: // attribute tag - throw OrthancException(ErrorCode_NotImplemented); - - - /** - * 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_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; + DcmTag key(tag.GetGroup(), tag.GetElement()); + if (tag.IsPrivate()) + { + return DcmItem::newDicomElement(key, privateCreator.c_str()); + } + else + { + return DcmItem::newDicomElement(key, NULL); } - - throw OrthancException(ErrorCode_InternalError); + +#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.0: 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 DicomTag& tag, const std::string& utf8Value, bool decodeDataUriScheme, Encoding dicomEncoding) @@ -1578,14 +1443,11 @@ decoded = &binary; } - DcmTag key(tag.GetGroup(), tag.GetElement()); - - if (tag.IsPrivate() || - IsBinaryTag(key)) + if (IsBinaryTag(element.getTag())) { bool ok; - switch (key.getEVR()) + switch (element.getTag().getEVR()) { case EVR_OW: if (decoded->size() % sizeof(Uint16) != 0) @@ -1619,7 +1481,7 @@ try { - switch (key.getEVR()) + switch (element.getTag().getEVR()) { // http://support.dcmtk.org/docs/dcvr_8h-source.html @@ -1628,7 +1490,6 @@ **/ case EVR_OB: // other byte - case EVR_OF: // other float case EVR_OW: // other word case EVR_AT: // attribute tag throw OrthancException(ErrorCode_NotImplemented); @@ -1683,6 +1544,9 @@ } case EVR_UL: // unsigned long +#if DCMTK_VERSION_NUMBER >= 361 + case EVR_OL: // other long (requires byte-swapping) +#endif { ok = element.putUint32(boost::lexical_cast(*decoded)).good(); break; @@ -1695,12 +1559,16 @@ } case EVR_FL: // float single-precision + case EVR_OF: // other float (requires byte swapping) { ok = element.putFloat32(boost::lexical_cast(*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(*decoded)).good(); break; @@ -1750,6 +1618,7 @@ 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) + "\""); @@ -1760,20 +1629,21 @@ DcmElement* FromDcmtkBridge::FromJson(const DicomTag& tag, const Json::Value& value, bool decodeDataUriScheme, - Encoding dicomEncoding) + Encoding dicomEncoding, + const std::string& privateCreator) { std::auto_ptr element; switch (value.type()) { case Json::stringValue: - element.reset(CreateElementForTag(tag)); - FillElementWithString(*element, tag, value.asString(), decodeDataUriScheme, dicomEncoding); + element.reset(CreateElementForTag(tag, privateCreator)); + FillElementWithString(*element, value.asString(), decodeDataUriScheme, dicomEncoding); break; case Json::nullValue: - element.reset(CreateElementForTag(tag)); - FillElementWithString(*element, tag, "", decodeDataUriScheme, dicomEncoding); + element.reset(CreateElementForTag(tag, privateCreator)); + FillElementWithString(*element, "", decodeDataUriScheme, dicomEncoding); break; case Json::arrayValue: @@ -1798,7 +1668,7 @@ 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)); + item->insert(FromJson(ParseTag(members[j]), value[i][members[j]], decodeDataUriScheme, dicomEncoding, privateCreator)); } break; } @@ -1907,7 +1777,8 @@ DcmDataset* FromDcmtkBridge::FromJson(const Json::Value& json, // Encoded using UTF-8 bool generateIdentifiers, bool decodeDataUriScheme, - Encoding defaultEncoding) + Encoding defaultEncoding, + const std::string& privateCreator) { std::auto_ptr result(new DcmDataset); Encoding encoding = ExtractEncoding(json, defaultEncoding); @@ -1945,7 +1816,7 @@ if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET) { - std::auto_ptr element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding)); + std::auto_ptr element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator)); const DcmTagKey& tag = element->getTag(); result->findAndDeleteElement(tag); @@ -2268,13 +2139,6 @@ **/ if (evr == EVR_OB || // other byte - evr == EVR_OF || // other float -#if DCMTK_VERSION_NUMBER >= 361 - evr == EVR_OD || // other double -#endif -#if DCMTK_VERSION_NUMBER >= 362 - evr == EVR_OL || // other long -#endif evr == EVR_OW || // other word evr == EVR_UN) // unknown value representation { @@ -2464,6 +2328,9 @@ } case EVR_UL: // unsigned long +#if DCMTK_VERSION_NUMBER >= 362 + case EVR_OL: +#endif { DcmUnsignedLong& content = dynamic_cast(element); @@ -2504,6 +2371,7 @@ } case EVR_FL: // float single-precision + case EVR_OF: { DcmFloatingPointSingle& content = dynamic_cast(element); @@ -2524,6 +2392,9 @@ } case EVR_FD: // float double-precision +#if DCMTK_VERSION_NUMBER >= 361 + case EVR_OD: +#endif { DcmFloatingPointDouble& content = dynamic_cast(element); diff -r a9ce35d67c3c -r 4922bdd046dd Core/DicomParsing/FromDcmtkBridge.h --- a/Core/DicomParsing/FromDcmtkBridge.h Tue Feb 25 13:57:43 2020 +0100 +++ b/Core/DicomParsing/FromDcmtkBridge.h Tue Feb 25 21:44:09 2020 +0100 @@ -209,10 +209,10 @@ static ValueRepresentation LookupValueRepresentation(const DicomTag& tag); - static DcmElement* CreateElementForTag(const DicomTag& tag); + static DcmElement* CreateElementForTag(const DicomTag& tag, + const std::string& privateCreator); static void FillElementWithString(DcmElement& element, - const DicomTag& tag, const std::string& utf8alue, // Encoded using UTF-8 bool decodeDataUriScheme, Encoding dicomEncoding); @@ -220,7 +220,8 @@ static DcmElement* FromJson(const DicomTag& tag, const Json::Value& element, // Encoded using UTF-8 bool decodeDataUriScheme, - Encoding dicomEncoding); + Encoding dicomEncoding, + const std::string& privateCreator); static DcmPixelSequence* GetPixelSequence(DcmDataset& dataset); @@ -230,7 +231,8 @@ static DcmDataset* FromJson(const Json::Value& json, // Encoded using UTF-8 bool generateIdentifiers, bool decodeDataUriScheme, - Encoding defaultEncoding); + Encoding defaultEncoding, + const std::string& privateCreator); static DcmFileFormat* LoadFromMemoryBuffer(const void* buffer, size_t size); diff -r a9ce35d67c3c -r 4922bdd046dd Core/DicomParsing/ITagVisitor.h --- a/Core/DicomParsing/ITagVisitor.h Tue Feb 25 13:57:43 2020 +0100 +++ b/Core/DicomParsing/ITagVisitor.h Tue Feb 25 21:44:09 2020 +0100 @@ -71,7 +71,7 @@ ValueRepresentation vr, const std::vector& values) = 0; - // FL, FD + // FL, FD, OD, OF virtual void VisitDoubles(const std::vector& parentTags, const std::vector& parentIndexes, const DicomTag& tag, @@ -84,7 +84,7 @@ const DicomTag& tag, const std::vector& values) = 0; - // OB, OD, OF, OL, OW, UN + // OB, OL, OW, UN virtual void VisitBinary(const std::vector& parentTags, const std::vector& parentIndexes, const DicomTag& tag, diff -r a9ce35d67c3c -r 4922bdd046dd Core/DicomParsing/ParsedDicomFile.cpp --- a/Core/DicomParsing/ParsedDicomFile.cpp Tue Feb 25 13:57:43 2020 +0100 +++ b/Core/DicomParsing/ParsedDicomFile.cpp Tue Feb 25 21:44:09 2020 +0100 @@ -619,7 +619,8 @@ void ParsedDicomFile::Insert(const DicomTag& tag, const Json::Value& value, - bool decodeDataUriScheme) + bool decodeDataUriScheme, + const std::string& privateCreator) { if (tag.GetElement() == 0x0000) { @@ -648,11 +649,38 @@ bool hasCodeExtensions; Encoding encoding = DetectEncoding(hasCodeExtensions); - std::auto_ptr element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding)); + std::auto_ptr element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator)); InsertInternal(*pimpl_->file_->getDataset(), element.release()); } + void ParsedDicomFile::ReplacePlainString(const DicomTag& tag, + const std::string& utf8Value) + { + if (tag.IsPrivate()) + { + throw OrthancException(ErrorCode_InternalError, + "Cannot apply this function to private tags: " + tag.Format()); + } + else + { + Replace(tag, utf8Value, false, DicomReplaceMode_InsertIfAbsent, + "" /* not a private tag, so no private creator */); + } + } + + + void ParsedDicomFile::SetIfAbsent(const DicomTag& tag, + const std::string& utf8Value) + { + std::string currentValue; + if (!GetTagValue(currentValue, tag)) + { + ReplacePlainString(tag, utf8Value); + } + } + + static bool CanReplaceProceed(DcmDataset& dicom, const DcmTagKey& tag, DicomReplaceMode mode) @@ -742,7 +770,8 @@ void ParsedDicomFile::Replace(const DicomTag& tag, const std::string& utf8Value, bool decodeDataUriScheme, - DicomReplaceMode mode) + DicomReplaceMode mode, + const std::string& privateCreator) { if (tag.GetElement() == 0x0000) { @@ -769,13 +798,13 @@ } } - std::auto_ptr element(FromDcmtkBridge::CreateElementForTag(tag)); + std::auto_ptr element(FromDcmtkBridge::CreateElementForTag(tag, privateCreator)); if (!utf8Value.empty()) { bool hasCodeExtensions; Encoding encoding = DetectEncoding(hasCodeExtensions); - FromDcmtkBridge::FillElementWithString(*element, tag, utf8Value, decodeDataUriScheme, encoding); + FromDcmtkBridge::FillElementWithString(*element, utf8Value, decodeDataUriScheme, encoding); } InsertInternal(dicom, element.release()); @@ -787,7 +816,8 @@ void ParsedDicomFile::Replace(const DicomTag& tag, const Json::Value& value, bool decodeDataUriScheme, - DicomReplaceMode mode) + DicomReplaceMode mode, + const std::string& privateCreator) { if (tag.GetElement() == 0x0000) { @@ -817,7 +847,7 @@ bool hasCodeExtensions; Encoding encoding = DetectEncoding(hasCodeExtensions); - InsertInternal(dicom, FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding)); + InsertInternal(dicom, FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator)); if (tag == DICOM_TAG_SOP_CLASS_UID || tag == DICOM_TAG_SOP_INSTANCE_UID) @@ -1483,7 +1513,8 @@ ParsedDicomFile* ParsedDicomFile::CreateFromJson(const Json::Value& json, - DicomFromJsonFlags flags) + DicomFromJsonFlags flags, + const std::string& privateCreator) { const bool generateIdentifiers = (flags & DicomFromJsonFlags_GenerateIdentifiers) ? true : false; const bool decodeDataUriScheme = (flags & DicomFromJsonFlags_DecodeDataUriScheme) ? true : false; @@ -1512,7 +1543,7 @@ } else if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET) { - result->Replace(tag, value, decodeDataUriScheme, DicomReplaceMode_InsertIfAbsent); + result->Replace(tag, value, decodeDataUriScheme, DicomReplaceMode_InsertIfAbsent, privateCreator); } } diff -r a9ce35d67c3c -r 4922bdd046dd Core/DicomParsing/ParsedDicomFile.h --- a/Core/DicomParsing/ParsedDicomFile.h Tue Feb 25 13:57:43 2020 +0100 +++ b/Core/DicomParsing/ParsedDicomFile.h Tue Feb 25 21:44:09 2020 +0100 @@ -138,32 +138,27 @@ void Replace(const DicomTag& tag, const std::string& utf8Value, bool decodeDataUriScheme, - DicomReplaceMode mode); + DicomReplaceMode mode, + const std::string& privateCreator /* used only for private tags */); void Replace(const DicomTag& tag, const Json::Value& value, // Assumed to be encoded with UTF-8 bool decodeDataUriScheme, - DicomReplaceMode mode); + DicomReplaceMode mode, + const std::string& privateCreator /* used only for private tags */); void Insert(const DicomTag& tag, const Json::Value& value, // Assumed to be encoded with UTF-8 - bool decodeDataUriScheme); + bool decodeDataUriScheme, + const std::string& privateCreator /* used only for private tags */); + // Cannot be applied to private tags void ReplacePlainString(const DicomTag& tag, - const std::string& utf8Value) - { - Replace(tag, utf8Value, false, DicomReplaceMode_InsertIfAbsent); - } + const std::string& utf8Value); + // Cannot be applied to private tags void SetIfAbsent(const DicomTag& tag, - const std::string& utf8Value) - { - std::string currentValue; - if (!GetTagValue(currentValue, tag)) - { - ReplacePlainString(tag, utf8Value); - } - } + const std::string& utf8Value); void RemovePrivateTags() { @@ -234,7 +229,8 @@ unsigned int GetFramesCount() const; static ParsedDicomFile* CreateFromJson(const Json::Value& value, - DicomFromJsonFlags flags); + DicomFromJsonFlags flags, + const std::string& privateCreator); void ChangeEncoding(Encoding target); diff -r a9ce35d67c3c -r 4922bdd046dd NEWS --- a/NEWS Tue Feb 25 13:57:43 2020 +0100 +++ b/NEWS Tue Feb 25 21:44:09 2020 +0100 @@ -13,6 +13,7 @@ * added "/instances/{id}/frames/{frame}/rendered" and "/instances/{id}/rendered" routes to render frames, taking windowing and resizing into account * "/instances": Support "Content-Encoding: gzip" to upload gzip-compressed DICOM files +* ".../modify" and "/tools/create-dicom": New option "PrivateCreator" for private tags Plugins ------- @@ -37,6 +38,7 @@ * More strict C-FIND SCP wrt. the DICOM standard: Forbid wildcard matching on some VRs, ignore main tags below the queried level * Fix issue #65 (Logging improvements) +* Fix issue #140 (Modifying private tags with REST API changes VR from LO to UN) * Fix issue #156 (Chunked Dicom-web transfer uses 100% CPU) * Fix issue #165 (Boundary parameter in multipart Content-Type is too long) * Fix issue #166 (CMake find_boost version is now broken with newer boost/cmake) diff -r a9ce35d67c3c -r 4922bdd046dd OrthancServer/OrthancFindRequestHandler.cpp --- a/OrthancServer/OrthancFindRequestHandler.cpp Tue Feb 25 13:57:43 2020 +0100 +++ b/OrthancServer/OrthancFindRequestHandler.cpp Tue Feb 25 21:44:09 2020 +0100 @@ -394,7 +394,8 @@ content.append(item); } - dicom.Replace(*tag, content, false, DicomReplaceMode_InsertIfAbsent); + dicom.Replace(*tag, content, false, DicomReplaceMode_InsertIfAbsent, + "" /* no private creator */); } } diff -r a9ce35d67c3c -r 4922bdd046dd OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Tue Feb 25 13:57:43 2020 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Tue Feb 25 21:44:09 2020 +0100 @@ -267,7 +267,8 @@ static void InjectTags(ParsedDicomFile& dicom, const Json::Value& tags, - bool decodeBinaryTags) + bool decodeBinaryTags, + const std::string& privateCreator) { if (tags.type() != Json::objectValue) { @@ -305,7 +306,7 @@ } else { - dicom.Replace(tag, tags[name], decodeBinaryTags, DicomReplaceMode_InsertIfAbsent); + dicom.Replace(tag, tags[name], decodeBinaryTags, DicomReplaceMode_InsertIfAbsent, privateCreator); } } } @@ -315,7 +316,8 @@ static void CreateSeries(RestApiPostCall& call, ParsedDicomFile& base /* in */, const Json::Value& content, - bool decodeBinaryTags) + bool decodeBinaryTags, + const std::string& privateCreator) { assert(content.isArray()); assert(content.size() > 0); @@ -348,7 +350,7 @@ if (content[i].isMember("Tags")) { - InjectTags(*dicom, content[i]["Tags"], decodeBinaryTags); + InjectTags(*dicom, content[i]["Tags"], decodeBinaryTags, privateCreator); } } @@ -538,6 +540,20 @@ decodeBinaryTags = v.asBool(); } + + // New argument in Orthanc 1.6.0 + std::string privateCreator; + if (request.isMember("PrivateCreator")) + { + const Json::Value& v = request["PrivateCreator"]; + if (v.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadRequest); + } + + privateCreator = v.asString(); + } + // Inject time-related information std::string date, time; @@ -565,7 +581,7 @@ } - InjectTags(dicom, request["Tags"], decodeBinaryTags); + InjectTags(dicom, request["Tags"], decodeBinaryTags, privateCreator); // Inject the content (either an image, or a PDF file) @@ -583,7 +599,7 @@ if (content.size() > 0) { // Let's create a series instead of a single instance - CreateSeries(call, dicom, content, decodeBinaryTags); + CreateSeries(call, dicom, content, decodeBinaryTags, privateCreator); return; } } diff -r a9ce35d67c3c -r 4922bdd046dd OrthancServer/OrthancRestApi/OrthancRestModalities.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Tue Feb 25 13:57:43 2020 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Tue Feb 25 21:44:09 2020 +0100 @@ -1277,7 +1277,8 @@ MyGetModalityUsingSymbolicName(call.GetUriComponent("id", "")); std::auto_ptr query - (ParsedDicomFile::CreateFromJson(json, static_cast(0))); + (ParsedDicomFile::CreateFromJson(json, static_cast(0), + "" /* no private creator */)); DicomFindAnswers answers(true); diff -r a9ce35d67c3c -r 4922bdd046dd OrthancServer/Search/HierarchicalMatcher.cpp --- a/OrthancServer/Search/HierarchicalMatcher.cpp Tue Feb 25 13:57:43 2020 +0100 +++ b/OrthancServer/Search/HierarchicalMatcher.cpp Tue Feb 25 21:44:09 2020 +0100 @@ -268,7 +268,13 @@ if (source.findAndGetElement(tag, element).good() && element != NULL) { - std::auto_ptr cloned(FromDcmtkBridge::CreateElementForTag(*it)); + if (it->IsPrivate()) + { + throw OrthancException(ErrorCode_NotImplemented, + "Not applicable to private tags: " + it->Format()); + } + + std::auto_ptr cloned(FromDcmtkBridge::CreateElementForTag(*it, "" /* no private creator */)); cloned->copyFrom(*element); target->insert(cloned.release()); } diff -r a9ce35d67c3c -r 4922bdd046dd Plugins/Engine/OrthancPlugins.cpp --- a/Plugins/Engine/OrthancPlugins.cpp Tue Feb 25 13:57:43 2020 +0100 +++ b/Plugins/Engine/OrthancPlugins.cpp Tue Feb 25 21:44:09 2020 +0100 @@ -824,7 +824,8 @@ Json::Value target; call.ExecuteToJson(target, true); - filtered_.reset(ParsedDicomFile::CreateFromJson(target, DicomFromJsonFlags_None)); + filtered_.reset(ParsedDicomFile::CreateFromJson(target, DicomFromJsonFlags_None, + "" /* no private creator */)); currentQuery_ = filtered_.get(); } } @@ -2922,7 +2923,8 @@ { std::auto_ptr file - (ParsedDicomFile::CreateFromJson(json, static_cast(p.flags))); + (ParsedDicomFile::CreateFromJson(json, static_cast(p.flags), + "" /* TODO - private creator */)); if (p.pixelData) { diff -r a9ce35d67c3c -r 4922bdd046dd Resources/Graveyard/FromDcmtkBridge.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/FromDcmtkBridge.cpp Tue Feb 25 21:44:09 2020 +0100 @@ -0,0 +1,168 @@ + DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag) + { + DcmTag key(tag.GetGroup(), tag.GetElement()); + + if (tag.IsPrivate()) + { + // This raises BitBucket issue 140 (Modifying private tags with + // REST API changes VR from LO to UN) + // https://bitbucket.org/sjodogne/orthanc/issues/140 + LOG(WARNING) << "You are using DCMTK < 3.6.1: All the private tags " + "are considered as having a binary value representation"; + return new DcmOtherByteOtherWord(key); + } + else if (IsBinaryTag(key)) + { + return new DcmOtherByteOtherWord(key); + } + + switch (key.getEVR()) + { + // http://support.dcmtk.org/docs/dcvr_8h-source.html + + /** + * Binary types, handled above + **/ + +#if DCMTK_VERSION_NUMBER >= 361 + case EVR_OD: +#endif + +#if DCMTK_VERSION_NUMBER >= 362 + case EVR_OL: +#endif + + 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 + throw OrthancException(ErrorCode_InternalError); + + + /** + * 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); + +#if DCMTK_VERSION_NUMBER >= 361 + case EVR_UC: // unlimited characters + return new DcmUnlimitedCharacters(key); +#endif + +#if DCMTK_VERSION_NUMBER >= 361 + case EVR_UR: // URI/URL + return new DcmUniversalResourceIdentifierOrLocator(key); +#endif + + + /** + * 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); + + + /** + * TODO + **/ + + case EVR_AT: // attribute tag + throw OrthancException(ErrorCode_NotImplemented); + + + /** + * 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_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); + } diff -r a9ce35d67c3c -r 4922bdd046dd UnitTestsSources/DicomMapTests.cpp --- a/UnitTestsSources/DicomMapTests.cpp Tue Feb 25 13:57:43 2020 +0100 +++ b/UnitTestsSources/DicomMapTests.cpp Tue Feb 25 21:44:09 2020 +0100 @@ -725,9 +725,9 @@ dicom.ReplacePlainString(DicomTag(0x0008, 0x0070), "LO"); dicom.ReplacePlainString(DicomTag(0x0010, 0x4000), "LT"); dicom.ReplacePlainString(DicomTag(0x0028, 0x2000), "OB"); - dicom.ReplacePlainString(DicomTag(0x7fe0, 0x0009), "OD"); - dicom.ReplacePlainString(DicomTag(0x0064, 0x0009), "OF"); - dicom.ReplacePlainString(DicomTag(0x0066, 0x0040), "46"); + dicom.ReplacePlainString(DicomTag(0x7fe0, 0x0009), "3.14159"); // OD (other double) + dicom.ReplacePlainString(DicomTag(0x0064, 0x0009), "2.71828"); // OF (other float) + dicom.ReplacePlainString(DicomTag(0x0066, 0x0040), "46"); // OL (other long) ASSERT_THROW(dicom.ReplacePlainString(DicomTag(0x0028, 0x1201), "O"), OrthancException); dicom.ReplacePlainString(DicomTag(0x0028, 0x1201), "OWOW"); dicom.ReplacePlainString(DicomTag(0x0010, 0x0010), "PN"); @@ -784,28 +784,25 @@ #if DCMTK_VERSION_NUMBER >= 361 ASSERT_EQ("OD", visitor.GetResult() ["7FE00009"]["vr"].asString()); + ASSERT_FLOAT_EQ(3.14159f, boost::lexical_cast(visitor.GetResult() ["7FE00009"]["Value"][0].asString())); #else ASSERT_EQ("UN", visitor.GetResult() ["7FE00009"]["vr"].asString()); + Toolbox::DecodeBase64(s, visitor.GetResult() ["7FE00009"]["InlineBinary"].asString()); + ASSERT_EQ(8u, s.size()); // Because of padding + ASSERT_EQ(0, s[7]); + ASSERT_EQ("3.14159", s.substr(0, 7)); #endif - Toolbox::DecodeBase64(s, visitor.GetResult() ["7FE00009"]["InlineBinary"].asString()); - ASSERT_EQ("OD", s); - ASSERT_EQ("OF", visitor.GetResult() ["00640009"]["vr"].asString()); - Toolbox::DecodeBase64(s, visitor.GetResult() ["00640009"]["InlineBinary"].asString()); - ASSERT_EQ("OF", s); + ASSERT_FLOAT_EQ(2.71828f, boost::lexical_cast(visitor.GetResult() ["00640009"]["Value"][0].asString())); #if DCMTK_VERSION_NUMBER < 361 ASSERT_EQ("UN", visitor.GetResult() ["00660040"]["vr"].asString()); Toolbox::DecodeBase64(s, visitor.GetResult() ["00660040"]["InlineBinary"].asString()); ASSERT_EQ("46", s); -#elif DCMTK_VERSION_NUMBER == 361 - ASSERT_EQ("UL", visitor.GetResult() ["00660040"]["vr"].asString()); +#else + ASSERT_EQ("OL", visitor.GetResult() ["00660040"]["vr"].asString()); ASSERT_EQ(46, visitor.GetResult() ["00660040"]["Value"][0].asInt()); -#elif DCMTK_VERSION_NUMBER > 361 - ASSERT_EQ("OL", visitor.GetResult() ["00660040"]["vr"].asString()); - Toolbox::DecodeBase64(s, visitor.GetResult() ["00660040"]["InlineBinary"].asString()); - ASSERT_EQ("46", s); #endif ASSERT_EQ("OW", visitor.GetResult() ["00281201"]["vr"].asString()); @@ -893,8 +890,18 @@ ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0070), false)); ASSERT_EQ("LO", s); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x4000), false)); ASSERT_EQ("LT", s); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0028, 0x2000), true)); ASSERT_EQ("OB", s); - ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x7fe0, 0x0009), true)); ASSERT_EQ("OD", s); - ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0064, 0x0009), true)); ASSERT_EQ("OF", s); + ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x7fe0, 0x0009), true)); + +#if DCMTK_VERSION_NUMBER >= 361 + ASSERT_FLOAT_EQ(3.14159f, boost::lexical_cast(s)); +#else + ASSERT_EQ(8u, s.size()); // Because of padding + ASSERT_EQ(0, s[7]); + ASSERT_EQ("3.14159", s.substr(0, 7)); +#endif + + ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0064, 0x0009), true)); + ASSERT_FLOAT_EQ(2.71828f, boost::lexical_cast(s)); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0028, 0x1201), true)); ASSERT_EQ("OWOW", s); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x0010), false)); ASSERT_EQ("PN", s); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0050), false)); ASSERT_EQ("SH", s); @@ -902,20 +909,23 @@ ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0018, 0x9219), false)); ASSERT_EQ("-16", s); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0081), false)); ASSERT_EQ("ST", s); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0013), false)); ASSERT_EQ("TM", s); - ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0119), false)); ASSERT_EQ("UC", s); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0016), false)); ASSERT_EQ("UI", s); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x1161), false)); ASSERT_EQ("128", s); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x4342, 0x1234), true)); ASSERT_EQ("UN", s); - ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0120), false)); ASSERT_EQ("UR", s); - ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0301), false)); ASSERT_EQ("17", s); ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0040, 0x0031), false)); ASSERT_EQ("UT", s); -#if DCMTK_VERSION_NUMBER == 361 - ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0066, 0x0040), false)); +#if DCMTK_VERSION_NUMBER >= 361 + ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0066, 0x0040), false)); ASSERT_EQ("46", s); + ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0119), false)); ASSERT_EQ("UC", s); + ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0120), false)); ASSERT_EQ("UR", s); + ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0301), false)); ASSERT_EQ("17", s); #else - ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0066, 0x0040), true)); + ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0066, 0x0040), true)); ASSERT_EQ("46", s); // OL + ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0119), true)); ASSERT_EQ("UC", s); + ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0120), true)); ASSERT_EQ("UR", s); + ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0301), true)); ASSERT_EQ("17", s); // US (but tag unknown to DCMTK 3.6.0) #endif - ASSERT_EQ("46", s); + } } diff -r a9ce35d67c3c -r 4922bdd046dd UnitTestsSources/FromDcmtkTests.cpp --- a/UnitTestsSources/FromDcmtkTests.cpp Tue Feb 25 13:57:43 2020 +0100 +++ b/UnitTestsSources/FromDcmtkTests.cpp Tue Feb 25 21:44:09 2020 +0100 @@ -108,7 +108,7 @@ { ASSERT_EQ(DICOM_TAG_PATIENT_NAME, FromDcmtkBridge::ParseTag("PatientName")); - const DicomTag privateTag(0x0045, 0x0010); + const DicomTag privateTag(0x0045, 0x1010); const DicomTag privateTag2(FromDcmtkBridge::ParseTag("0031-1020")); ASSERT_TRUE(privateTag.IsPrivate()); ASSERT_TRUE(privateTag2.IsPrivate()); @@ -119,19 +119,19 @@ ParsedDicomFile o(true); o.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "coucou"); ASSERT_FALSE(o.GetTagValue(s, privateTag)); - o.Insert(privateTag, "private tag", false); + o.Insert(privateTag, "private tag", false, "OrthancCreator"); ASSERT_TRUE(o.GetTagValue(s, privateTag)); ASSERT_STREQ("private tag", s.c_str()); ASSERT_FALSE(o.GetTagValue(s, privateTag2)); - ASSERT_THROW(o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_ThrowIfAbsent), OrthancException); + ASSERT_THROW(o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_ThrowIfAbsent, "OrthancCreator"), OrthancException); ASSERT_FALSE(o.GetTagValue(s, privateTag2)); - o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_IgnoreIfAbsent); + o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_IgnoreIfAbsent, "OrthancCreator"); ASSERT_FALSE(o.GetTagValue(s, privateTag2)); - o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_InsertIfAbsent); + o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_InsertIfAbsent, "OrthancCreator"); ASSERT_TRUE(o.GetTagValue(s, privateTag2)); ASSERT_STREQ("hello", s.c_str()); - o.ReplacePlainString(privateTag2, "hello world"); + o.Replace(privateTag2, std::string("hello world"), false, DicomReplaceMode_InsertIfAbsent, "OrthancCreator"); ASSERT_TRUE(o.GetTagValue(s, privateTag2)); ASSERT_STREQ("hello world", s.c_str()); @@ -290,7 +290,7 @@ f.SetEncoding(testEncodings[i]); std::string s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i], false); - f.Insert(DICOM_TAG_PATIENT_NAME, s, false); + f.Insert(DICOM_TAG_PATIENT_NAME, s, false, ""); f.SaveToMemoryBuffer(dicom); } @@ -407,7 +407,7 @@ { Json::Value a; a = "Hello"; - element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false, Encoding_Utf8)); + element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false, Encoding_Utf8, "")); Json::Value b; std::set ignoreTagLength; @@ -439,20 +439,20 @@ 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); + 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); + 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)); + element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, true, Encoding_Utf8, "")); Json::Value b; std::set ignoreTagLength; @@ -464,7 +464,7 @@ { Json::Value a = Json::arrayValue; CreateSampleJson(a); - element.reset(FromDcmtkBridge::FromJson(REFERENCED_STUDY_SEQUENCE, a, true, Encoding_Utf8)); + element.reset(FromDcmtkBridge::FromJson(REFERENCED_STUDY_SEQUENCE, a, true, Encoding_Utf8, "")); { Json::Value b; @@ -506,8 +506,8 @@ { ParsedDicomFile f(true); - f.Insert(DICOM_TAG_PATIENT_NAME, "World", false); - ASSERT_THROW(f.Insert(DICOM_TAG_PATIENT_ID, "Hello", false), OrthancException); // Already existing tag + f.Insert(DICOM_TAG_PATIENT_NAME, "World", false, ""); + ASSERT_THROW(f.Insert(DICOM_TAG_PATIENT_ID, "Hello", false, ""), OrthancException); // Already existing tag f.ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, "Toto"); // (*) f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "Tata"); // (**) @@ -515,16 +515,16 @@ ASSERT_FALSE(f.LookupTransferSyntax(s)); ASSERT_THROW(f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"), - false, DicomReplaceMode_ThrowIfAbsent), OrthancException); - f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"), false, DicomReplaceMode_IgnoreIfAbsent); + false, DicomReplaceMode_ThrowIfAbsent, ""), OrthancException); + f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"), false, DicomReplaceMode_IgnoreIfAbsent, ""); ASSERT_FALSE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER)); - f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"), false, DicomReplaceMode_InsertIfAbsent); + f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"), false, DicomReplaceMode_InsertIfAbsent, ""); ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER)); ASSERT_EQ(s, "Accession"); - f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession2"), false, DicomReplaceMode_IgnoreIfAbsent); + f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession2"), false, DicomReplaceMode_IgnoreIfAbsent, ""); ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER)); ASSERT_EQ(s, "Accession2"); - f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession3"), false, DicomReplaceMode_ThrowIfAbsent); + f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession3"), false, DicomReplaceMode_ThrowIfAbsent, ""); ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER)); ASSERT_EQ(s, "Accession3"); @@ -552,20 +552,20 @@ ASSERT_FALSE(f.HasTag(REFERENCED_STUDY_SEQUENCE)); f.Remove(REFERENCED_STUDY_SEQUENCE); // No effect - f.Insert(REFERENCED_STUDY_SEQUENCE, a, true); + f.Insert(REFERENCED_STUDY_SEQUENCE, a, true, ""); ASSERT_TRUE(f.HasTag(REFERENCED_STUDY_SEQUENCE)); - ASSERT_THROW(f.Insert(REFERENCED_STUDY_SEQUENCE, a, true), OrthancException); + 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); + 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_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); + 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); + f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_InsertIfAbsent, ""); ASSERT_TRUE(f.HasTag(REFERENCED_PATIENT_SEQUENCE)); { @@ -580,8 +580,8 @@ } a = "data:application/octet-stream;base64,VGF0YQ=="; // echo -n "Tata" | base64 - f.Replace(DICOM_TAG_SOP_INSTANCE_UID, a, false, DicomReplaceMode_InsertIfAbsent); // (*) - f.Replace(DICOM_TAG_SOP_CLASS_UID, a, true, DicomReplaceMode_InsertIfAbsent); // (**) + f.Replace(DICOM_TAG_SOP_INSTANCE_UID, a, false, DicomReplaceMode_InsertIfAbsent, ""); // (*) + f.Replace(DICOM_TAG_SOP_CLASS_UID, a, true, DicomReplaceMode_InsertIfAbsent, ""); // (**) std::string s; ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_INSTANCE_UID)); @@ -614,7 +614,7 @@ } Json::Value s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i], false); - f.Replace(DICOM_TAG_PATIENT_NAME, s, false, DicomReplaceMode_InsertIfAbsent); + f.Replace(DICOM_TAG_PATIENT_NAME, s, false, DicomReplaceMode_InsertIfAbsent, ""); Json::Value v; f.DatasetToJson(v, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0); @@ -626,13 +626,13 @@ TEST(ParsedDicomFile, ToJsonFlags1) { - FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7053, 0x1000), ValueRepresentation_PersonName, "MyPrivateTag", 1, 1, ""); + FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7053, 0x1000), ValueRepresentation_OtherByte, "MyPrivateTag", 1, 1, "OrthancCreator"); FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7050, 0x1000), ValueRepresentation_PersonName, "Declared public tag", 1, 1, ""); ParsedDicomFile f(true); - f.Insert(DicomTag(0x7050, 0x1000), "Some public tag", false); // Even group => public tag - f.Insert(DicomTag(0x7052, 0x1000), "Some unknown tag", false); // Even group => public, unknown tag - f.Insert(DicomTag(0x7053, 0x1000), "Some private tag", false); // Odd group => private tag + f.Insert(DicomTag(0x7050, 0x1000), "Some public tag", false, ""); // Even group => public tag + f.Insert(DicomTag(0x7052, 0x1000), "Some unknown tag", false, ""); // Even group => public, unknown tag + f.Insert(DicomTag(0x7053, 0x1000), "Some private tag", false, "OrthancCreator"); // Odd group => private tag Json::Value v; f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0); @@ -644,7 +644,7 @@ ASSERT_EQ(Json::stringValue, v["7050,1000"].type()); ASSERT_EQ("Some public tag", v["7050,1000"].asString()); - f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_ConvertBinaryToNull), 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_IncludeBinary | DicomToJsonFlags_ConvertBinaryToNull), 0); ASSERT_EQ(Json::objectValue, v.type()); ASSERT_EQ(7u, v.getMemberNames().size()); ASSERT_FALSE(v.isMember("7052,1000")); @@ -653,7 +653,14 @@ ASSERT_EQ("Some public tag", v["7050,1000"].asString()); ASSERT_EQ(Json::nullValue, v["7053,1000"].type()); - f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludePrivateTags, 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludePrivateTags), 0); + ASSERT_EQ(Json::objectValue, v.type()); + ASSERT_EQ(6u, v.getMemberNames().size()); + ASSERT_FALSE(v.isMember("7052,1000")); + ASSERT_TRUE(v.isMember("7050,1000")); + ASSERT_FALSE(v.isMember("7053,1000")); + + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_IncludeBinary), 0); ASSERT_EQ(Json::objectValue, v.type()); ASSERT_EQ(7u, v.getMemberNames().size()); ASSERT_FALSE(v.isMember("7052,1000")); @@ -666,7 +673,7 @@ ASSERT_EQ("application/octet-stream", mime); ASSERT_EQ("Some private tag", content); - f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_ConvertBinaryToNull), 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludeBinary | DicomToJsonFlags_ConvertBinaryToNull), 0); ASSERT_EQ(Json::objectValue, v.type()); ASSERT_EQ(7u, v.getMemberNames().size()); ASSERT_TRUE(v.isMember("7050,1000")); @@ -675,7 +682,7 @@ ASSERT_EQ("Some public tag", v["7050,1000"].asString()); ASSERT_EQ(Json::nullValue, v["7052,1000"].type()); - f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludeUnknownTags), 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludeBinary), 0); ASSERT_EQ(Json::objectValue, v.type()); ASSERT_EQ(7u, v.getMemberNames().size()); ASSERT_TRUE(v.isMember("7050,1000")); @@ -687,7 +694,7 @@ ASSERT_EQ("application/octet-stream", mime); ASSERT_EQ("Some unknown tag", content); - f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_ConvertBinaryToNull), 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_IncludeBinary | DicomToJsonFlags_ConvertBinaryToNull), 0); ASSERT_EQ(Json::objectValue, v.type()); ASSERT_EQ(8u, v.getMemberNames().size()); ASSERT_TRUE(v.isMember("7050,1000")); @@ -702,7 +709,7 @@ TEST(ParsedDicomFile, ToJsonFlags2) { ParsedDicomFile f(true); - f.Insert(DICOM_TAG_PIXEL_DATA, "Pixels", false); + f.Insert(DICOM_TAG_PIXEL_DATA, "Pixels", false, ""); Json::Value v; f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0); @@ -811,7 +818,7 @@ { std::auto_ptr dicom - (ParsedDicomFile::CreateFromJson(v, static_cast(DicomFromJsonFlags_GenerateIdentifiers))); + (ParsedDicomFile::CreateFromJson(v, static_cast(DicomFromJsonFlags_GenerateIdentifiers), "")); Json::Value vv; dicom->DatasetToJson(vv, DicomToJsonFormat_Human, toJsonFlags, 0); @@ -827,7 +834,7 @@ { std::auto_ptr dicom - (ParsedDicomFile::CreateFromJson(v, static_cast(DicomFromJsonFlags_GenerateIdentifiers))); + (ParsedDicomFile::CreateFromJson(v, static_cast(DicomFromJsonFlags_GenerateIdentifiers), "")); Json::Value vv; dicom->DatasetToJson(vv, DicomToJsonFormat_Human, static_cast(DicomToJsonFlags_IncludePixelData), 0); @@ -841,7 +848,7 @@ { std::auto_ptr dicom - (ParsedDicomFile::CreateFromJson(v, static_cast(DicomFromJsonFlags_DecodeDataUriScheme))); + (ParsedDicomFile::CreateFromJson(v, static_cast(DicomFromJsonFlags_DecodeDataUriScheme), "")); Json::Value vv; dicom->DatasetToJson(vv, DicomToJsonFormat_Short, toJsonFlags, 0); diff -r a9ce35d67c3c -r 4922bdd046dd UnitTestsSources/MultiThreadingTests.cpp --- a/UnitTestsSources/MultiThreadingTests.cpp Tue Feb 25 13:57:43 2020 +0100 +++ b/UnitTestsSources/MultiThreadingTests.cpp Tue Feb 25 21:44:09 2020 +0100 @@ -1143,9 +1143,9 @@ Json::Value s; ParsedDicomFile source(true); - source.Insert(DICOM_TAG_STUDY_DESCRIPTION, "Test 1", false); - source.Insert(DICOM_TAG_SERIES_DESCRIPTION, "Test 2", false); - source.Insert(DICOM_TAG_PATIENT_NAME, "Test 3", false); + source.Insert(DICOM_TAG_STUDY_DESCRIPTION, "Test 1", false, ""); + source.Insert(DICOM_TAG_SERIES_DESCRIPTION, "Test 2", false, ""); + source.Insert(DICOM_TAG_PATIENT_NAME, "Test 3", false, ""); std::auto_ptr modified(source.Clone(true)); @@ -1310,7 +1310,7 @@ // Create a sample DICOM file ParsedDicomFile dicom(true); dicom.Replace(DICOM_TAG_PATIENT_NAME, std::string("JODOGNE"), - false, DicomReplaceMode_InsertIfAbsent); + false, DicomReplaceMode_InsertIfAbsent, ""); DicomInstanceToStore toStore; toStore.SetParsedDicomFile(dicom);