# HG changeset patch # User Sebastien Jodogne # Date 1528386461 -7200 # Node ID 47d812308d637fd01a427b6b63c676158e35c69c # Parent 27b7884512befef48f0adda9d5738f8da4eaed36 serialization of DicomModification diff -r 27b7884512be -r 47d812308d63 Core/DicomFormat/DicomTag.cpp --- a/Core/DicomFormat/DicomTag.cpp Thu Jun 07 12:51:44 2018 +0200 +++ b/Core/DicomFormat/DicomTag.cpp Thu Jun 07 17:47:41 2018 +0200 @@ -39,9 +39,32 @@ #include #include #include +#include namespace Orthanc -{ +{ + static inline uint16_t GetCharValue(char c) + { + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else + return 0; + } + + + static inline uint16_t GetTagValue(const char* c) + { + return ((GetCharValue(c[0]) << 12) + + (GetCharValue(c[1]) << 8) + + (GetCharValue(c[2]) << 4) + + GetCharValue(c[3])); + } + + bool DicomTag::operator< (const DicomTag& other) const { if (group_ < other.group_) @@ -74,6 +97,49 @@ } + bool DicomTag::ParseHexadecimal(DicomTag& tag, + const char* value) + { + size_t length = strlen(value); + + if (length == 9 && + isxdigit(value[0]) && + isxdigit(value[1]) && + isxdigit(value[2]) && + isxdigit(value[3]) && + (value[4] == '-' || value[4] == ',') && + isxdigit(value[5]) && + isxdigit(value[6]) && + isxdigit(value[7]) && + isxdigit(value[8])) + { + uint16_t group = GetTagValue(value); + uint16_t element = GetTagValue(value + 5); + tag = DicomTag(group, element); + return true; + } + else if (length == 8 && + isxdigit(value[0]) && + isxdigit(value[1]) && + isxdigit(value[2]) && + isxdigit(value[3]) && + isxdigit(value[4]) && + isxdigit(value[5]) && + isxdigit(value[6]) && + isxdigit(value[7])) + { + uint16_t group = GetTagValue(value); + uint16_t element = GetTagValue(value + 4); + tag = DicomTag(group, element); + return true; + } + else + { + return false; + } + } + + const char* DicomTag::GetMainTagsName() const { if (*this == DICOM_TAG_ACCESSION_NUMBER) diff -r 27b7884512be -r 47d812308d63 Core/DicomFormat/DicomTag.h --- a/Core/DicomFormat/DicomTag.h Thu Jun 07 12:51:44 2018 +0200 +++ b/Core/DicomFormat/DicomTag.h Thu Jun 07 17:47:41 2018 +0200 @@ -88,6 +88,9 @@ std::string Format() const; + static bool ParseHexadecimal(DicomTag& tag, + const char* value); + friend std::ostream& operator<< (std::ostream& o, const DicomTag& tag); static void AddTagsForModule(std::set& target, diff -r 27b7884512be -r 47d812308d63 Core/DicomParsing/DicomModification.cpp --- a/Core/DicomParsing/DicomModification.cpp Thu Jun 07 12:51:44 2018 +0200 +++ b/Core/DicomParsing/DicomModification.cpp Thu Jun 07 17:47:41 2018 +0200 @@ -36,6 +36,7 @@ #include "../Logging.h" #include "../OrthancException.h" +#include "../SerializationToolbox.h" #include "FromDcmtkBridge.h" #include "ITagVisitor.h" @@ -1238,15 +1239,164 @@ GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName); } + + + + static const char* REMOVE_PRIVATE_TAGS = "RemovePrivateTags"; + static const char* LEVEL = "Level"; + static const char* ALLOW_MANUAL_IDENTIFIERS = "AllowManualIdentifiers"; + static const char* KEEP_STUDY_INSTANCE_UID = "KeepStudyInstanceUID"; + static const char* KEEP_SERIES_INSTANCE_UID = "KeepSeriesInstanceUID"; + static const char* UPDATE_REFERENCED_RELATIONSHIPS = "UpdateReferencedRelationships"; + static const char* REMOVALS = "Removals"; + static const char* CLEARINGS = "Clearings"; + static const char* PRIVATE_TAGS_TO_KEEP = "PrivateTagsToKeep"; + static const char* REPLACEMENTS = "Replacements"; + static const char* MAP_PATIENTS = "MapPatients"; + static const char* MAP_STUDIES = "MapStudies"; + static const char* MAP_SERIES = "MapSeries"; + static const char* MAP_INSTANCES = "MapInstances"; void DicomModification::Serialize(Json::Value& value) const { - throw OrthancException(ErrorCode_NotImplemented); + if (identifierGenerator_ != NULL) + { + LOG(ERROR) << "Cannot serialize a DicomModification with a custom identifier generator"; + throw OrthancException(ErrorCode_InternalError); + } + + value = Json::objectValue; + value[REMOVE_PRIVATE_TAGS] = removePrivateTags_; + value[LEVEL] = EnumerationToString(level_); + value[ALLOW_MANUAL_IDENTIFIERS] = allowManualIdentifiers_; + value[KEEP_STUDY_INSTANCE_UID] = keepStudyInstanceUid_; + value[KEEP_SERIES_INSTANCE_UID] = keepSeriesInstanceUid_; + value[UPDATE_REFERENCED_RELATIONSHIPS] = updateReferencedRelationships_; + + SerializationToolbox::WriteSetOfTags(value, removals_, REMOVALS); + SerializationToolbox::WriteSetOfTags(value, clearings_, CLEARINGS); + SerializationToolbox::WriteSetOfTags(value, privateTagsToKeep_, PRIVATE_TAGS_TO_KEEP); + + Json::Value& tmp = value[REPLACEMENTS]; + + tmp = Json::objectValue; + + for (Replacements::const_iterator it = replacements_.begin(); + it != replacements_.end(); ++it) + { + assert(it->second != NULL); + tmp[it->first.Format()] = *it->second; + } + + Json::Value& mapPatients = value[MAP_PATIENTS]; + Json::Value& mapStudies = value[MAP_STUDIES]; + Json::Value& mapSeries = value[MAP_SERIES]; + Json::Value& mapInstances = value[MAP_INSTANCES]; + + mapPatients = Json::objectValue; + mapStudies = Json::objectValue; + mapSeries = Json::objectValue; + mapInstances = Json::objectValue; + + for (UidMap::const_iterator it = uidMap_.begin(); it != uidMap_.end(); ++it) + { + Json::Value* tmp = NULL; + + switch (it->first.first) + { + case ResourceType_Patient: + tmp = &mapPatients; + break; + + case ResourceType_Study: + tmp = &mapStudies; + break; + + case ResourceType_Series: + tmp = &mapSeries; + break; + + case ResourceType_Instance: + tmp = &mapInstances; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + assert(tmp != NULL); + (*tmp) [it->first.second] = it->second; + } + } + + + void DicomModification::UnserializeUidMap(ResourceType level, + const Json::Value& serialized, + const char* field) + { + if (!serialized.isMember(field) || + serialized[field].type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + Json::Value::Members names = serialized[field].getMemberNames(); + + for (Json::Value::Members::const_iterator it = names.begin(); it != names.end(); ++it) + { + const Json::Value& value = serialized[field][*it]; + + if (value.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + uidMap_[std::make_pair(level, *it)] = value.asString(); + } + } } DicomModification::DicomModification(const Json::Value& serialized) { - throw OrthancException(ErrorCode_NotImplemented); + removePrivateTags_ = SerializationToolbox::ReadBoolean(serialized, REMOVE_PRIVATE_TAGS); + level_ = StringToResourceType(SerializationToolbox::ReadString(serialized, LEVEL).c_str()); + allowManualIdentifiers_ = SerializationToolbox::ReadBoolean(serialized, ALLOW_MANUAL_IDENTIFIERS); + keepStudyInstanceUid_ = SerializationToolbox::ReadBoolean(serialized, KEEP_STUDY_INSTANCE_UID); + keepSeriesInstanceUid_ = SerializationToolbox::ReadBoolean(serialized, KEEP_SERIES_INSTANCE_UID); + updateReferencedRelationships_ = SerializationToolbox::ReadBoolean + (serialized, UPDATE_REFERENCED_RELATIONSHIPS); + + SerializationToolbox::ReadSetOfTags(removals_, serialized, REMOVALS); + SerializationToolbox::ReadSetOfTags(clearings_, serialized, CLEARINGS); + SerializationToolbox::ReadSetOfTags(privateTagsToKeep_, serialized, PRIVATE_TAGS_TO_KEEP); + + if (!serialized.isMember(REPLACEMENTS) || + serialized[REPLACEMENTS].type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + Json::Value::Members names = serialized[REPLACEMENTS].getMemberNames(); + + for (Json::Value::Members::const_iterator it = names.begin(); it != names.end(); ++it) + { + DicomTag tag(0, 0); + if (!DicomTag::ParseHexadecimal(tag, it->c_str())) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + const Json::Value& value = serialized[REPLACEMENTS][*it]; + replacements_.insert(std::make_pair(tag, new Json::Value(value))); + } + } + + UnserializeUidMap(ResourceType_Patient, serialized, MAP_PATIENTS); + UnserializeUidMap(ResourceType_Study, serialized, MAP_STUDIES); + UnserializeUidMap(ResourceType_Series, serialized, MAP_SERIES); + UnserializeUidMap(ResourceType_Instance, serialized, MAP_INSTANCES); } } diff -r 27b7884512be -r 47d812308d63 Core/DicomParsing/DicomModification.h --- a/Core/DicomParsing/DicomModification.h Thu Jun 07 12:51:44 2018 +0200 +++ b/Core/DicomParsing/DicomModification.h Thu Jun 07 17:47:41 2018 +0200 @@ -107,6 +107,10 @@ void SetupAnonymization2017c(); + void UnserializeUidMap(ResourceType level, + const Json::Value& serialized, + const char* field); + public: DicomModification(); diff -r 27b7884512be -r 47d812308d63 Core/DicomParsing/FromDcmtkBridge.cpp --- a/Core/DicomParsing/FromDcmtkBridge.cpp Thu Jun 07 12:51:44 2018 +0200 +++ b/Core/DicomParsing/FromDcmtkBridge.cpp Thu Jun 07 17:47:41 2018 +0200 @@ -106,27 +106,6 @@ namespace Orthanc { - static inline uint16_t GetCharValue(char c) - { - if (c >= '0' && c <= '9') - return c - '0'; - else if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - else if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - else - return 0; - } - - static inline uint16_t GetTagValue(const char* c) - { - return ((GetCharValue(c[0]) << 12) + - (GetCharValue(c[1]) << 8) + - (GetCharValue(c[2]) << 4) + - GetCharValue(c[3])); - } - - #if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary, EmbeddedResources::FileResourceId resource) @@ -1061,35 +1040,10 @@ DicomTag FromDcmtkBridge::ParseTag(const char* name) { - if (strlen(name) == 9 && - isxdigit(name[0]) && - isxdigit(name[1]) && - isxdigit(name[2]) && - isxdigit(name[3]) && - (name[4] == '-' || name[4] == ',') && - isxdigit(name[5]) && - isxdigit(name[6]) && - isxdigit(name[7]) && - isxdigit(name[8])) + DicomTag parsed(0, 0); + if (DicomTag::ParseHexadecimal(parsed, name)) { - uint16_t group = GetTagValue(name); - uint16_t element = GetTagValue(name + 5); - return DicomTag(group, element); - } - - if (strlen(name) == 8 && - isxdigit(name[0]) && - isxdigit(name[1]) && - isxdigit(name[2]) && - isxdigit(name[3]) && - isxdigit(name[4]) && - isxdigit(name[5]) && - isxdigit(name[6]) && - isxdigit(name[7])) - { - uint16_t group = GetTagValue(name); - uint16_t element = GetTagValue(name + 4); - return DicomTag(group, element); + return parsed; } #if 0 diff -r 27b7884512be -r 47d812308d63 Core/SerializationToolbox.cpp --- a/Core/SerializationToolbox.cpp Thu Jun 07 12:51:44 2018 +0200 +++ b/Core/SerializationToolbox.cpp Thu Jun 07 17:47:41 2018 +0200 @@ -164,6 +164,38 @@ } + void ReadSetOfTags(std::set& target, + const Json::Value& value, + const std::string& field) + { + if (value.type() != Json::objectValue || + !value.isMember(field.c_str()) || + value[field.c_str()].type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + const Json::Value& arr = value[field.c_str()]; + + target.clear(); + + for (Json::Value::ArrayIndex i = 0; i < arr.size(); i++) + { + DicomTag tag(0, 0); + + if (arr[i].type() != Json::stringValue || + !DicomTag::ParseHexadecimal(tag, arr[i].asCString())) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + target.insert(tag); + } + } + } + + void WriteArrayOfStrings(Json::Value& target, const std::vector& values, const std::string& field) @@ -174,15 +206,13 @@ throw OrthancException(ErrorCode_BadFileFormat); } - Json::Value tmp; + Json::Value& value = target[field]; - tmp = Json::arrayValue; + value = Json::arrayValue; for (size_t i = 0; i < values.size(); i++) { - tmp.append(values[i]); + value.append(values[i]); } - - target[field] = tmp; } @@ -190,15 +220,43 @@ const std::set& values, const std::string& field) { - Json::Value v = Json::arrayValue; + if (target.type() != Json::objectValue || + target.isMember(field.c_str())) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + Json::Value& value = target[field]; + + value = Json::arrayValue; for (std::set::const_iterator it = values.begin(); it != values.end(); ++it) { - v.append(*it); + value.append(*it); } - - target[field] = v; + } + + + void WriteSetOfTags(Json::Value& target, + const std::set& tags, + const std::string& field) + { + if (target.type() != Json::objectValue || + target.isMember(field.c_str())) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + Json::Value& value = target[field]; + + value = Json::arrayValue; + + for (std::set::const_iterator it = tags.begin(); + it != tags.end(); ++it) + { + value.append(it->Format()); + } } } } diff -r 27b7884512be -r 47d812308d63 Core/SerializationToolbox.h --- a/Core/SerializationToolbox.h Thu Jun 07 12:51:44 2018 +0200 +++ b/Core/SerializationToolbox.h Thu Jun 07 17:47:41 2018 +0200 @@ -33,9 +33,10 @@ #pragma once +#include "DicomFormat/DicomTag.h" + #include #include -#include namespace Orthanc { @@ -65,6 +66,10 @@ const Json::Value& value, const std::string& field); + void ReadSetOfTags(std::set& target, + const Json::Value& value, + const std::string& field); + void WriteArrayOfStrings(Json::Value& target, const std::vector& values, const std::string& field); @@ -72,5 +77,9 @@ void WriteSetOfStrings(Json::Value& target, const std::set& values, const std::string& field); + + void WriteSetOfTags(Json::Value& target, + const std::set& tags, + const std::string& field); } } diff -r 27b7884512be -r 47d812308d63 UnitTestsSources/MultiThreadingTests.cpp --- a/UnitTestsSources/MultiThreadingTests.cpp Thu Jun 07 12:51:44 2018 +0200 +++ b/UnitTestsSources/MultiThreadingTests.cpp Thu Jun 07 17:47:41 2018 +0200 @@ -891,9 +891,60 @@ } +static bool IsSameTagValue(ParsedDicomFile& dicom1, + ParsedDicomFile& dicom2, + DicomTag tag) +{ + std::string a, b; + return (dicom1.GetTagValue(a, tag) && + dicom2.GetTagValue(b, tag) && + (a == b)); +} + + + TEST(JobsSerialization, DicomModification) { - // TODO : Test serialization of DicomModification + 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); + + std::auto_ptr modified(source.Clone(true)); + + { + DicomModification modification; + modification.SetLevel(ResourceType_Series); + modification.Clear(DICOM_TAG_STUDY_DESCRIPTION); + modification.Remove(DICOM_TAG_SERIES_DESCRIPTION); + modification.Replace(DICOM_TAG_PATIENT_NAME, "Test 4", true); + + modification.Apply(*modified); + modification.Serialize(s); + } + + { + DicomModification modification(s); + ASSERT_EQ(ResourceType_Series, modification.GetLevel()); + + std::auto_ptr second(source.Clone(true)); + modification.Apply(*second); + + std::string s; + ASSERT_TRUE(second->GetTagValue(s, DICOM_TAG_STUDY_DESCRIPTION)); + ASSERT_TRUE(s.empty()); + ASSERT_FALSE(second->GetTagValue(s, DICOM_TAG_SERIES_DESCRIPTION)); + ASSERT_TRUE(second->GetTagValue(s, DICOM_TAG_PATIENT_NAME)); + ASSERT_EQ("Test 4", s); + + ASSERT_TRUE(IsSameTagValue(source, *modified, DICOM_TAG_STUDY_INSTANCE_UID)); + ASSERT_TRUE(IsSameTagValue(source, *second, DICOM_TAG_STUDY_INSTANCE_UID)); + + ASSERT_FALSE(IsSameTagValue(source, *second, DICOM_TAG_SERIES_INSTANCE_UID)); + ASSERT_TRUE(IsSameTagValue(*modified, *second, DICOM_TAG_SERIES_INSTANCE_UID)); + } } @@ -1071,11 +1122,11 @@ ASSERT_EQ("c", tmp.GetPostArgument(0)); } - // TODO : ModifyInstanceOperation + // ModifyInstanceOperation - /* { std::auto_ptr modification(new DicomModification); + modification->SetupAnonymization(DicomVersion_2008); ModifyInstanceOperation operation(GetContext(), RequestOrigin_Lua, modification.release()); operation.Serialize(s); @@ -1086,7 +1137,8 @@ const ModifyInstanceOperation& tmp = dynamic_cast(*operation); ASSERT_EQ(RequestOrigin_Lua, tmp.GetRequestOrigin()); - }*/ + ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION)); + } }