changeset 2662:47d812308d63 jobs

serialization of DicomModification
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 07 Jun 2018 17:47:41 +0200
parents 27b7884512be
children 228e2783ce83
files Core/DicomFormat/DicomTag.cpp Core/DicomFormat/DicomTag.h Core/DicomParsing/DicomModification.cpp Core/DicomParsing/DicomModification.h Core/DicomParsing/FromDcmtkBridge.cpp Core/SerializationToolbox.cpp Core/SerializationToolbox.h UnitTestsSources/MultiThreadingTests.cpp
diffstat 8 files changed, 362 insertions(+), 66 deletions(-) [+]
line wrap: on
line diff
--- 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 <iostream>
 #include <iomanip>
 #include <stdio.h>
+#include <string.h>
 
 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)
--- 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<DicomTag>& target,
--- 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);
   }
 }
--- 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();
 
--- 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
--- 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<DicomTag>& 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<std::string>& 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<std::string>& 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<std::string>::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<DicomTag>& 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<DicomTag>::const_iterator it = tags.begin();
+           it != tags.end(); ++it)
+      {
+        value.append(it->Format());
+      }
     }
   }
 }
--- 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 <json/value.h>
 #include <list>
-#include <set>
 
 namespace Orthanc
 {
@@ -65,6 +66,10 @@
                           const Json::Value& value,
                           const std::string& field);
 
+    void ReadSetOfTags(std::set<DicomTag>& target,
+                       const Json::Value& value,
+                       const std::string& field);
+
     void WriteArrayOfStrings(Json::Value& target,
                              const std::vector<std::string>& values,
                              const std::string& field);
@@ -72,5 +77,9 @@
     void WriteSetOfStrings(Json::Value& target,
                            const std::set<std::string>& values,
                            const std::string& field);
+
+    void WriteSetOfTags(Json::Value& target,
+                        const std::set<DicomTag>& tags,
+                        const std::string& field);
   }
 }
--- 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<ParsedDicomFile> 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<ParsedDicomFile> 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<DicomModification> 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<ModifyInstanceOperation&>(*operation);
     ASSERT_EQ(RequestOrigin_Lua, tmp.GetRequestOrigin());
-    }*/
+    ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION));
+  }
 }