changeset 1693:558b25228a23

creation of tag hierarchy from json
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 08 Oct 2015 13:45:33 +0200
parents 4eaf164dd574
children 06d579e82bb8
files OrthancServer/FromDcmtkBridge.cpp OrthancServer/FromDcmtkBridge.h OrthancServer/ParsedDicomFile.cpp OrthancServer/ParsedDicomFile.h UnitTestsSources/FromDcmtkTests.cpp
diffstat 5 files changed, 239 insertions(+), 66 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancServer/FromDcmtkBridge.cpp	Thu Oct 08 10:57:29 2015 +0200
+++ b/OrthancServer/FromDcmtkBridge.cpp	Thu Oct 08 13:45:33 2015 +0200
@@ -1012,11 +1012,21 @@
   }
 
 
+  static bool IsBinaryTag(const DcmTag& key)
+  {
+    return key.isPrivate() || key.isUnknownVR();
+  }
+
 
   DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag)
   {
     DcmTag key(tag.GetGroup(), tag.GetElement());
 
+    if (IsBinaryTag(key))
+    {
+      return new DcmOtherByteOtherWord(key);
+    }
+
     switch (key.getEVR())
     {
       // http://support.dcmtk.org/docs/dcvr_8h-source.html
@@ -1163,9 +1173,9 @@
       decoded = &binary;
     }
 
+    DcmTag key(tag.GetGroup(), tag.GetElement());
 
-    if (FromDcmtkBridge::IsPrivateTag(tag) ||
-        FromDcmtkBridge::IsUnknownTag(tag))
+    if (IsBinaryTag(key))
     {
       if (element.putUint8Array((const Uint8*) decoded->c_str(), decoded->size()).good())
       {
@@ -1177,7 +1187,6 @@
       }
     }
 
-    DcmTag key(tag.GetGroup(), tag.GetElement());
     bool ok = false;
     
     try
@@ -1314,8 +1323,8 @@
   }
 
 
-  DcmElement* FromDcmtkBridge::FromJson(const Json::Value& value,
-                                        const DicomTag& tag,
+  DcmElement* FromDcmtkBridge::FromJson(const DicomTag& tag,
+                                        const Json::Value& value,
                                         bool decodeBinaryTags)
   {
     std::auto_ptr<DcmElement> element;
@@ -1345,7 +1354,7 @@
           Json::Value::Members members = value[i].getMemberNames();
           for (Json::Value::ArrayIndex j = 0; j < members.size(); j++)
           {
-            item->insert(FromJson(value[i][members[j]], ParseTag(members[j]), decodeBinaryTags));
+            item->insert(FromJson(ParseTag(members[j]), value[i][members[j]], decodeBinaryTags));
           }
 
           sequence->append(item.release());
--- a/OrthancServer/FromDcmtkBridge.h	Thu Oct 08 10:57:29 2015 +0200
+++ b/OrthancServer/FromDcmtkBridge.h	Thu Oct 08 13:45:33 2015 +0200
@@ -129,8 +129,8 @@
                                       const std::string& value,
                                       bool interpretBinaryTags);
 
-    static DcmElement* FromJson(const Json::Value& element,
-                                const DicomTag& tag,
+    static DcmElement* FromJson(const DicomTag& tag,
+                                const Json::Value& element,
                                 bool interpretBinaryTags);
   };
 }
--- a/OrthancServer/ParsedDicomFile.cpp	Thu Oct 08 10:57:29 2015 +0200
+++ b/OrthancServer/ParsedDicomFile.cpp	Thu Oct 08 13:45:33 2015 +0200
@@ -137,6 +137,7 @@
 
 #include <boost/math/special_functions/round.hpp>
 #include <dcmtk/dcmdata/dcostrmb.h>
+#include <boost/algorithm/string/predicate.hpp>
 
 
 static const char* CONTENT_TYPE_OCTET_STREAM = "application/octet-stream";
@@ -579,52 +580,49 @@
   }
 
 
-  void ParsedDicomFile::Insert(const DicomTag& tag,
-                               const std::string& value)
+  static void InsertInternal(DcmDataset& dicom,
+                             DcmElement* element)
   {
-    OFCondition cond;
-
-    if (FromDcmtkBridge::IsPrivateTag(tag) ||
-        FromDcmtkBridge::IsUnknownTag(tag))
-    {
-      // This is a private tag
-      // http://support.dcmtk.org/redmine/projects/dcmtk/wiki/howto_addprivatedata
-
-      DcmTag key(tag.GetGroup(), tag.GetElement(), EVR_OB);
-      cond = pimpl_->file_->getDataset()->putAndInsertUint8Array
-        (key, (const Uint8*) value.c_str(), value.size(), false);
-    }
-    else
-    {
-      std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag));
-      FromDcmtkBridge::FillElementWithString(*element, tag, value, false);
-
-      cond = pimpl_->file_->getDataset()->insert(element.release(), false, false);
-    }
-
+    OFCondition cond = dicom.insert(element, false, false);
     if (!cond.good())
     {
       // This field already exists
+      delete element;
       throw OrthancException(ErrorCode_InternalError);
     }
   }
 
 
-  void ParsedDicomFile::Replace(const DicomTag& tag,
-                                const std::string& value,
-                                DicomReplaceMode mode)
+  void ParsedDicomFile::Insert(const DicomTag& tag,
+                               const std::string& value)
+  {
+    std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag));
+    FromDcmtkBridge::FillElementWithString(*element, tag, value, false);
+    InsertInternal(*pimpl_->file_->getDataset(), element.release());
+  }
+
+
+  void ParsedDicomFile::Insert(const DicomTag& tag,
+                               const Json::Value& value,
+                               bool decodeBinaryTags)
   {
-    DcmTagKey key(tag.GetGroup(), tag.GetElement());
-    DcmElement* element = NULL;
+    std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeBinaryTags));
+    InsertInternal(*pimpl_->file_->getDataset(), element.release());
+  }
+
 
-    if (!pimpl_->file_->getDataset()->findAndGetElement(key, element).good() ||
-        element == NULL)
+  static void ReplaceInternal(DcmDataset& dicom,
+                              std::auto_ptr<DcmElement>& element,
+                              DicomReplaceMode mode)
+  {
+    const DcmTagKey& tag = element->getTag();
+
+    if (!dicom.findAndDeleteElement(tag).good())
     {
       // This field does not exist, act wrt. the specified "mode"
       switch (mode)
       {
         case DicomReplaceMode_InsertIfAbsent:
-          Insert(tag, value);
           break;
 
         case DicomReplaceMode_ThrowIfAbsent:
@@ -634,11 +632,33 @@
           return;
       }
     }
-    else
+
+    // Either the tag was not existing, or the replace mode was set to
+    // "InsertIfAbsent"
+    InsertInternal(dicom, element.release());
+  }
+
+
+  void ParsedDicomFile::UpdateStorageUid(const DicomTag& tag,
+                                         const std::string& value,
+                                         bool decodeBinaryTags)
+  {
+    if (tag != DICOM_TAG_SOP_CLASS_UID &&
+        tag != DICOM_TAG_SOP_INSTANCE_UID)
     {
-      FromDcmtkBridge::FillElementWithString(*element, tag, value, false);
+      return;
     }
 
+    std::string binary;
+    const std::string* decoded = &value;
+
+    if (decodeBinaryTags &&
+        boost::starts_with(value, "data:application/octet-stream;base64,"))
+    {
+      std::string mime;
+      Toolbox::DecodeDataUriScheme(mime, binary, value);
+      decoded = &binary;
+    }
 
     /**
      * dcmodify will automatically correct 'Media Storage SOP Class
@@ -651,12 +671,44 @@
 
     if (tag == DICOM_TAG_SOP_CLASS_UID)
     {
-      Replace(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, value, DicomReplaceMode_InsertIfAbsent);
+      Replace(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, *decoded, DicomReplaceMode_InsertIfAbsent);
     }
 
     if (tag == DICOM_TAG_SOP_INSTANCE_UID)
     {
-      Replace(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, value, DicomReplaceMode_InsertIfAbsent);
+      Replace(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, *decoded, DicomReplaceMode_InsertIfAbsent);
+    }    
+  }
+
+
+  void ParsedDicomFile::Replace(const DicomTag& tag,
+                                const std::string& value,
+                                DicomReplaceMode mode)
+  {
+    std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag));
+    FromDcmtkBridge::FillElementWithString(*element, tag, value, false);
+    ReplaceInternal(*pimpl_->file_->getDataset(), element, mode);
+    UpdateStorageUid(tag, value, false);
+  }
+
+    
+  void ParsedDicomFile::Replace(const DicomTag& tag,
+                                const Json::Value& value,
+                                bool decodeBinaryTags,
+                                DicomReplaceMode mode)
+  {
+    std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeBinaryTags));
+    ReplaceInternal(*pimpl_->file_->getDataset(), element, mode);
+
+    if (tag == DICOM_TAG_SOP_CLASS_UID ||
+        tag == DICOM_TAG_SOP_INSTANCE_UID)
+    {
+      if (value.type() != Json::stringValue)
+      {
+        throw OrthancException(ErrorCode_BadParameterType);
+      }
+
+      UpdateStorageUid(tag, value.asString(), decodeBinaryTags);
     }
   }
 
@@ -865,7 +917,7 @@
     }
     else
     {
-      LOG(ERROR) << "Unsupported MIME type for the content of a new DICOM file";
+      LOG(ERROR) << "Unsupported MIME type for the content of a new DICOM file: " << mime;
       throw OrthancException(ErrorCode_NotImplemented);
     }
   }
--- a/OrthancServer/ParsedDicomFile.h	Thu Oct 08 10:57:29 2015 +0200
+++ b/OrthancServer/ParsedDicomFile.h	Thu Oct 08 13:45:33 2015 +0200
@@ -53,6 +53,10 @@
 
     void RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep);
 
+    void UpdateStorageUid(const DicomTag& tag,
+                          const std::string& value,
+                          bool decodeBinaryTags);
+
   public:
     ParsedDicomFile();  // Create a minimal DICOM instance
 
@@ -77,6 +81,15 @@
     void Insert(const DicomTag& tag,
                 const std::string& value);
 
+    void Insert(const DicomTag& tag,
+                const Json::Value& value,
+                bool decodeBinaryTags);
+
+    void Replace(const DicomTag& tag,
+                 const Json::Value& value,
+                 bool decodeBinaryTags,
+                 DicomReplaceMode mode = DicomReplaceMode_InsertIfAbsent);
+
     void Replace(const DicomTag& tag,
                  const std::string& value,
                  DicomReplaceMode mode = DicomReplaceMode_InsertIfAbsent);
--- a/UnitTestsSources/FromDcmtkTests.cpp	Thu Oct 08 10:57:29 2015 +0200
+++ b/UnitTestsSources/FromDcmtkTests.cpp	Thu Oct 08 13:45:33 2015 +0200
@@ -304,16 +304,37 @@
 }
 
 
+
+static const DicomTag REFERENCED_STUDY_SEQUENCE(0x0008, 0x1110);
+static const DicomTag REFERENCED_PATIENT_SEQUENCE(0x0008, 0x1120);
+
+static void CreateSampleJson(Json::Value& a)
+{
+  {
+    Json::Value b = Json::objectValue;
+    b["PatientName"] = "Hello";
+    b["PatientID"] = "World";
+    b["StudyDescription"] = "Toto";
+    a.append(b);
+  }
+
+  {
+    Json::Value b = Json::objectValue;
+    b["PatientName"] = "data:application/octet-stream;base64,SGVsbG8y";  // echo -n "Hello2" | base64
+    b["PatientID"] = "World2";
+    a.append(b);
+  }
+}
+
+
 TEST(FromDcmtkBridge, FromJson)
 {
-  const DicomTag REFERENCED_STUDY_SEQUENCE(0x0008, 0x1110);
-
   std::auto_ptr<DcmElement> element;
 
   {
     Json::Value a;
     a = "Hello";
-    element.reset(FromDcmtkBridge::FromJson(a, DICOM_TAG_PATIENT_NAME, false));
+    element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false));
 
     Json::Value b;
     FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, 0, Encoding_Ascii);
@@ -324,20 +345,20 @@
     Json::Value a;
     a = "Hello";
     // Cannot assign a string to a sequence
-    ASSERT_THROW(element.reset(FromDcmtkBridge::FromJson(a, REFERENCED_STUDY_SEQUENCE, false)), OrthancException);
+    ASSERT_THROW(element.reset(FromDcmtkBridge::FromJson(REFERENCED_STUDY_SEQUENCE, a, false)), OrthancException);
   }
 
   {
     Json::Value a = Json::arrayValue;
     a.append("Hello");
     // Cannot assign an array to a string
-    ASSERT_THROW(element.reset(FromDcmtkBridge::FromJson(a, DICOM_TAG_PATIENT_NAME, false)), OrthancException);
+    ASSERT_THROW(element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false)), OrthancException);
   }
 
   {
     Json::Value a;
     a = "data:application/octet-stream;base64,SGVsbG8=";  // echo -n "Hello" | base64
-    element.reset(FromDcmtkBridge::FromJson(a, DICOM_TAG_PATIENT_NAME, true));
+    element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, true));
 
     Json::Value b;
     FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, 0, Encoding_Ascii);
@@ -346,23 +367,8 @@
 
   {
     Json::Value a = Json::arrayValue;
-
-    {
-      Json::Value b = Json::objectValue;
-      b["PatientName"] = "Hello";
-      b["PatientID"] = "World";
-      b["StudyDescription"] = "Toto";
-      a.append(b);
-    }
-
-    {
-      Json::Value b = Json::objectValue;
-      b["PatientName"] = "Hello2";
-      b["PatientID"] = "World2";
-      a.append(b);
-    }
-
-    element.reset(FromDcmtkBridge::FromJson(a, REFERENCED_STUDY_SEQUENCE, false));
+    CreateSampleJson(a);
+    element.reset(FromDcmtkBridge::FromJson(REFERENCED_STUDY_SEQUENCE, a, true));
 
     {
       Json::Value b;
@@ -388,7 +394,100 @@
       Json::Value c;
       SimplifyTags(c, b);
 
+      a[1]["PatientName"] = "Hello2";  // To remove the Data URI Scheme encoding
       ASSERT_EQ(0, c["ReferencedStudySequence"].compare(a));
     }
   }
 }
+
+
+
+TEST(ParsedDicomFile, InsertReplaceStrings)
+{
+  ParsedDicomFile f;
+
+  f.Insert(DICOM_TAG_PATIENT_NAME, "World");
+  ASSERT_THROW(f.Insert(DICOM_TAG_PATIENT_ID, "Hello"), OrthancException);  // Already existing tag
+  f.Replace(DICOM_TAG_SOP_INSTANCE_UID, "Toto");  // (*)
+  f.Replace(DICOM_TAG_SOP_CLASS_UID, "Tata");  // (**)
+
+  std::string s;
+
+  ASSERT_THROW(f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession", DicomReplaceMode_ThrowIfAbsent), OrthancException);
+  f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession", DicomReplaceMode_IgnoreIfAbsent);
+  ASSERT_FALSE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
+  f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession", DicomReplaceMode_InsertIfAbsent);
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
+  ASSERT_EQ(s, "Accession");
+  f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession2", DicomReplaceMode_IgnoreIfAbsent);
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
+  ASSERT_EQ(s, "Accession2");
+  f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession3", DicomReplaceMode_ThrowIfAbsent);
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
+  ASSERT_EQ(s, "Accession3");
+
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_PATIENT_NAME));
+  ASSERT_EQ(s, "World");
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_INSTANCE_UID));
+  ASSERT_EQ(s, "Toto");
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID));  // Implicitly modified by (*)
+  ASSERT_EQ(s, "Toto");
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_CLASS_UID));
+  ASSERT_EQ(s, "Tata");
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID));  // Implicitly modified by (**)
+  ASSERT_EQ(s, "Tata");
+}
+
+
+
+
+TEST(ParsedDicomFile, InsertReplaceJson)
+{
+  ParsedDicomFile f;
+
+  Json::Value a;
+  CreateSampleJson(a);
+
+  ASSERT_FALSE(f.HasTag(REFERENCED_STUDY_SEQUENCE));
+  f.Remove(REFERENCED_STUDY_SEQUENCE);  // No effect
+  f.Insert(REFERENCED_STUDY_SEQUENCE, a, true);
+  ASSERT_TRUE(f.HasTag(REFERENCED_STUDY_SEQUENCE));
+  ASSERT_THROW(f.Insert(REFERENCED_STUDY_SEQUENCE, a, true), OrthancException);
+  f.Remove(REFERENCED_STUDY_SEQUENCE);
+  ASSERT_FALSE(f.HasTag(REFERENCED_STUDY_SEQUENCE));
+  f.Insert(REFERENCED_STUDY_SEQUENCE, a, true);
+  ASSERT_TRUE(f.HasTag(REFERENCED_STUDY_SEQUENCE));
+
+  ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE));
+  ASSERT_THROW(f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_ThrowIfAbsent), OrthancException);
+  ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE));
+  f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_IgnoreIfAbsent);
+  ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE));
+  f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_InsertIfAbsent);
+  ASSERT_TRUE(f.HasTag(REFERENCED_PATIENT_SEQUENCE));
+
+  {
+    Json::Value b;
+    f.ToJson(b, DicomToJsonFormat_Full, 0);
+
+    Json::Value c;
+    SimplifyTags(c, b);
+
+    ASSERT_EQ(0, c["ReferencedPatientSequence"].compare(a));
+    ASSERT_NE(0, c["ReferencedStudySequence"].compare(a));  // Because Data URI Scheme decoding was enabled
+  }
+
+  a = "data:application/octet-stream;base64,VGF0YQ==";   // echo -n "Tata" | base64 
+  f.Replace(DICOM_TAG_SOP_INSTANCE_UID, a, false);  // (*)
+  f.Replace(DICOM_TAG_SOP_CLASS_UID, a, true);  // (**)
+
+  std::string s;
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_INSTANCE_UID));
+  ASSERT_EQ(s, a.asString());
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID));  // Implicitly modified by (*)
+  ASSERT_EQ(s, a.asString());
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_CLASS_UID));
+  ASSERT_EQ(s, "Tata");
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID));  // Implicitly modified by (**)
+  ASSERT_EQ(s, "Tata");
+}