# HG changeset patch # User Sebastien Jodogne # Date 1623252284 -7200 # Node ID fcd2dc7c8f311ae1ad5c1878d1521549c5fdc181 # Parent 693f049729bac81f578647f6c93c907b1ab327d4 "Replace", "Keep" and "Remove" in "/modify" and "/anonymize" accept paths to subsequences diff -r 693f049729ba -r fcd2dc7c8f31 NEWS --- a/NEWS Tue Jun 08 18:28:57 2021 +0200 +++ b/NEWS Wed Jun 09 17:24:44 2021 +0200 @@ -10,9 +10,12 @@ REST API -------- +* API version upgraded to 13 * ZIP archive/media generated in synchronous mode are now streamed by default * "Replace" tags in "/modify" and "/anonymize" now supports value representation AT * "/jobs/..." has new field "ErrorDetails" to help identify the cause of an error +* "Replace", "Keep" and "Remove" in "/modify" and "/anonymize" accept paths to subsequences + using the syntax of the dcmodify command-line tool (wildcards are supported as well) Maintenance ----------- diff -r 693f049729ba -r fcd2dc7c8f31 OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake --- a/OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake Tue Jun 08 18:28:57 2021 +0200 +++ b/OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake Wed Jun 09 17:24:44 2021 +0200 @@ -37,7 +37,7 @@ # Version of the Orthanc API, can be retrieved from "/system" URI in # order to check whether new URI endpoints are available even if using # the mainline version of Orthanc -set(ORTHANC_API_VERSION "12") +set(ORTHANC_API_VERSION "13") ##################################################################### diff -r 693f049729ba -r fcd2dc7c8f31 OrthancFramework/Sources/DicomParsing/DicomModification.cpp --- a/OrthancFramework/Sources/DicomParsing/DicomModification.cpp Tue Jun 08 18:28:57 2021 +0200 +++ b/OrthancFramework/Sources/DicomParsing/DicomModification.cpp Wed Jun 09 17:24:44 2021 +0200 @@ -1076,9 +1076,11 @@ std::string name = query[i].asString(); - DicomTag tag = FromDcmtkBridge::ParseTag(name); + const DicomPath path(DicomPath::Parse(name)); - if (!force && IsDatabaseKey(tag)) + if (path.GetPrefixLength() == 0 && + !force && + IsDatabaseKey(path.GetFinalTag())) { throw OrthancException(ErrorCode_BadRequest, "Marking tag \"" + name + "\" as to be " + @@ -1089,13 +1091,13 @@ switch (operation) { case DicomModification::TagOperation_Keep: - target.Keep(tag); - LOG(TRACE) << "Keep: " << name << " (" << tag.Format() << ")"; + target.Keep(path); + LOG(TRACE) << "Keep: " << name << " = " << path.Format(); break; case DicomModification::TagOperation_Remove: - target.Remove(tag); - LOG(TRACE) << "Remove: " << name << " (" << tag.Format() << ")"; + target.Remove(path); + LOG(TRACE) << "Remove: " << name << " = " << path.Format(); break; default: @@ -1120,19 +1122,21 @@ const std::string& name = members[i]; const Json::Value& value = replacements[name]; - DicomTag tag = FromDcmtkBridge::ParseTag(name); + const DicomPath path(DicomPath::Parse(name)); - if (!force && IsDatabaseKey(tag)) + if (path.GetPrefixLength() == 0 && + !force && + IsDatabaseKey(path.GetFinalTag())) { throw OrthancException(ErrorCode_BadRequest, "Marking tag \"" + name + "\" as to be replaced " + "requires the \"Force\" option to be set to true"); } - - target.Replace(tag, value, false /* not safe for anonymization */); + + target.Replace(path, value, false /* not safe for anonymization */); - LOG(TRACE) << "Replace: " << name << " (" << tag.Format() - << ") == " << value.toStyledString(); + LOG(TRACE) << "Replace: " << name << " = " << path.Format() + << " by: " << value.toStyledString(); } } @@ -1282,9 +1286,12 @@ 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 - static const char* UIDS = "Uids"; // New in Orthanc 1.9.4 - static const char* REMOVED_RANGES = "RemovedRanges"; // New in Orthanc 1.9.4 + static const char* PRIVATE_CREATOR = "PrivateCreator"; // New in Orthanc 1.6.0 + static const char* UIDS = "Uids"; // New in Orthanc 1.9.4 + static const char* REMOVED_RANGES = "RemovedRanges"; // New in Orthanc 1.9.4 + static const char* KEEP_SEQUENCES = "KeepSequences"; // New in Orthanc 1.9.4 + static const char* REMOVE_SEQUENCES = "RemoveSequences"; // New in Orthanc 1.9.4 + static const char* SEQUENCE_REPLACEMENTS = "SequenceReplacements"; // New in Orthanc 1.9.4 void DicomModification::Serialize(Json::Value& value) const { @@ -1377,6 +1384,37 @@ } value[REMOVED_RANGES] = ranges; + + // New in Orthanc 1.9.4 + Json::Value lst = Json::arrayValue; + for (ListOfPaths::const_iterator it = keepSequences_.begin(); it != keepSequences_.end(); ++it) + { + assert(it->GetPrefixLength() > 0); + lst.append(it->Format()); + } + + value[KEEP_SEQUENCES] = lst; + + // New in Orthanc 1.9.4 + lst = Json::arrayValue; + for (ListOfPaths::const_iterator it = removeSequences_.begin(); it != removeSequences_.end(); ++it) + { + assert(it->GetPrefixLength() > 0); + lst.append(it->Format()); + } + + value[REMOVE_SEQUENCES] = lst; + + // New in Orthanc 1.9.4 + lst = Json::objectValue; + for (SequenceReplacements::const_iterator it = sequenceReplacements_.begin(); it != sequenceReplacements_.end(); ++it) + { + assert(*it != NULL); + assert((*it)->GetPath().GetPrefixLength() > 0); + lst[(*it)->GetPath().Format()] = (*it)->GetValue(); + } + + value[SEQUENCE_REPLACEMENTS] = lst; } void DicomModification::UnserializeUidMap(ResourceType level, @@ -1511,6 +1549,76 @@ } } } + + // New in Orthanc 1.9.4 + if (serialized.isMember(KEEP_SEQUENCES)) + { + const Json::Value& keep = serialized[KEEP_SEQUENCES]; + + if (keep.type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + for (Json::Value::ArrayIndex i = 0; i < keep.size(); i++) + { + if (keep[i].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + keepSequences_.push_back(DicomPath::Parse(keep[i].asString())); + } + } + } + } + + // New in Orthanc 1.9.4 + if (serialized.isMember(REMOVE_SEQUENCES)) + { + const Json::Value& remove = serialized[REMOVE_SEQUENCES]; + + if (remove.type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + for (Json::Value::ArrayIndex i = 0; i < remove.size(); i++) + { + if (remove[i].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + removeSequences_.push_back(DicomPath::Parse(remove[i].asString())); + } + } + } + } + + // New in Orthanc 1.9.4 + if (serialized.isMember(SEQUENCE_REPLACEMENTS)) + { + const Json::Value& replace = serialized[SEQUENCE_REPLACEMENTS]; + + if (replace.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + Json::Value::Members members = replace.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + sequenceReplacements_.push_back( + new SequenceReplacement(DicomPath::Parse(members[i]), replace[members[i]])); + } + } + } } diff -r 693f049729ba -r fcd2dc7c8f31 OrthancFramework/UnitTestsSources/JobsTests.cpp --- a/OrthancFramework/UnitTestsSources/JobsTests.cpp Tue Jun 08 18:28:57 2021 +0200 +++ b/OrthancFramework/UnitTestsSources/JobsTests.cpp Wed Jun 09 17:24:44 2021 +0200 @@ -1090,6 +1090,32 @@ } +TEST(JobsSerialization, DicomModification2) +{ + Json::Value s; + + { + DicomModification modification; + modification.SetupAnonymization(DicomVersion_2017c); + modification.Remove(DicomPath(DICOM_TAG_REFERENCED_IMAGE_SEQUENCE, 1, DICOM_TAG_SOP_INSTANCE_UID)); + modification.Replace(DicomPath(DICOM_TAG_REFERENCED_IMAGE_SEQUENCE, 1, DICOM_TAG_SOP_CLASS_UID), "Hello", true); + modification.Keep(DicomPath(DICOM_TAG_REFERENCED_IMAGE_SEQUENCE, 1, DICOM_TAG_PATIENT_NAME)); + + s = 42; + modification.Serialize(s); + } + + { + DicomModification modification(s); + + // Check idempotent serialization + Json::Value ss; + modification.Serialize(ss); + ASSERT_EQ(s.toStyledString(), ss.toStyledString()); + } +} + + TEST(JobsSerialization, Registry) { Json::Value s; diff -r 693f049729ba -r fcd2dc7c8f31 OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp Tue Jun 08 18:28:57 2021 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp Wed Jun 09 17:24:44 2021 +0200 @@ -46,6 +46,10 @@ #include #include +#define INFO_SUBSEQUENCES \ + "Starting with Orthanc 1.9.4, paths to subsequences can be provided using the "\ + "same syntax as the `dcmodify` command-line tool (wildcards are supported as well)." + namespace Orthanc { // Modification of DICOM instances ------------------------------------------ @@ -71,9 +75,9 @@ .SetRequestField("RemovePrivateTags", RestApiCallDocumentation::Type_Boolean, "Remove the private tags from the DICOM instances (defaults to `false`)", false) .SetRequestField("Replace", RestApiCallDocumentation::Type_JsonObject, - "Associative array to change the value of some DICOM tags in the DICOM instances", false) + "Associative array to change the value of some DICOM tags in the DICOM instances. " INFO_SUBSEQUENCES, false) .SetRequestField("Remove", RestApiCallDocumentation::Type_JsonListOfStrings, - "List of tags that must be removed from the DICOM instances", false) + "List of tags that must be removed from the DICOM instances. " INFO_SUBSEQUENCES, false) .SetRequestField("Keep", RestApiCallDocumentation::Type_JsonListOfStrings, "Keep the original value of the specified tags, to be chosen among the `StudyInstanceUID`, " "`SeriesInstanceUID` and `SOPInstanceUID` tags. Avoid this feature as much as possible, " @@ -96,11 +100,11 @@ .SetRequestField("KeepPrivateTags", RestApiCallDocumentation::Type_Boolean, "Keep the private tags from the DICOM instances (defaults to `false`)", false) .SetRequestField("Replace", RestApiCallDocumentation::Type_JsonObject, - "Associative array to change the value of some DICOM tags in the DICOM instances", false) + "Associative array to change the value of some DICOM tags in the DICOM instances. " INFO_SUBSEQUENCES, false) .SetRequestField("Remove", RestApiCallDocumentation::Type_JsonListOfStrings, - "List of additional tags to be removed from the DICOM instances", false) + "List of additional tags to be removed from the DICOM instances. " INFO_SUBSEQUENCES, false) .SetRequestField("Keep", RestApiCallDocumentation::Type_JsonListOfStrings, - "List of DICOM tags whose value must not be destroyed by the anonymization", false) + "List of DICOM tags whose value must not be destroyed by the anonymization. " INFO_SUBSEQUENCES, false) .SetRequestField("PrivateCreator", RestApiCallDocumentation::Type_String, "The private creator to be used for private tags in `Replace`", false); }