changeset 1701:4aaaecae5803 db-changes

integration mainline->db-changes
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 12 Oct 2015 14:47:58 +0200
parents 21d31da73374 (current diff) f5ddbd9239dd (diff)
children 9980875edc7c
files OrthancServer/Internals/StoreScp.cpp OrthancServer/OrthancRestApi/OrthancRestResources.cpp OrthancServer/ParsedDicomFile.cpp OrthancServer/ServerContext.cpp OrthancServer/ServerEnumerations.h UnitTestsSources/FromDcmtkTests.cpp
diffstat 21 files changed, 1152 insertions(+), 528 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Tue Oct 06 14:44:52 2015 +0200
+++ b/NEWS	Mon Oct 12 14:47:58 2015 +0200
@@ -3,6 +3,9 @@
 
 * Add ".dcm" suffix to files in ZIP archives (cf. URI ".../archive")
 * "/tools/create-dicom": Support of binary tags encoded using data URI scheme
+* "/tools/create-dicom": Support of hierarchical structures (creation of sequences)
+* "/modify" can insert/modify sequences
+* New URIs for attachments: ".../compress", ".../uncompress" and ".../is-compressed"
 
 Plugins
 -------
@@ -18,6 +21,7 @@
 Maintenance
 -----------
 
+* Fix issue 4 (C-Store Association not renegotiated on Specific-to-specific transfer syntax change)
 * "/system" URI gives information about the plugins used for storage area and DB back-end
 * Plugin callbacks should now return explicit "OrthancPluginErrorCode" instead of integers
 * "/tools/create-dicom" can create tags with unknown VR
--- a/OrthancServer/DicomInstanceToStore.cpp	Tue Oct 06 14:44:52 2015 +0200
+++ b/OrthancServer/DicomInstanceToStore.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -109,7 +109,8 @@
     if (!json_.HasContent())
     {
       json_.Allocate();
-      FromDcmtkBridge::ToJson(json_.GetContent(), GetDataset(parsed_.GetContent()));
+      FromDcmtkBridge::ToJson(json_.GetContent(), GetDataset(parsed_.GetContent()), 
+                              DicomToJsonFormat_Full, 256 /* max string length */);
     }
   }
 
--- a/OrthancServer/DicomModification.cpp	Tue Oct 06 14:44:52 2015 +0200
+++ b/OrthancServer/DicomModification.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -44,13 +44,56 @@
 
 namespace Orthanc
 {
+  void DicomModification::RemoveInternal(const DicomTag& tag)
+  {
+    Replacements::iterator it = replacements_.find(tag);
+
+    if (it != replacements_.end())
+    {
+      delete it->second;
+      replacements_.erase(it);
+    }    
+  }
+
+
+  void DicomModification::ReplaceInternal(const DicomTag& tag,
+                                          const Json::Value& value)
+  {
+    Replacements::iterator it = replacements_.find(tag);
+
+    if (it != replacements_.end())
+    {
+      delete it->second;
+      it->second = NULL;   // In the case of an exception during the clone
+      it->second = new Json::Value(value);  // Clone
+    }
+    else
+    {
+      replacements_[tag] = new Json::Value(value);  // Clone
+    }
+  }
+
+
+  void DicomModification::ClearReplacements()
+  {
+    for (Replacements::iterator it = replacements_.begin();
+         it != replacements_.end(); ++it)
+    {
+      delete it->second;
+    }
+
+    replacements_.clear();
+  }
+
+
   void DicomModification::MarkNotOrthancAnonymization()
   {
     Replacements::iterator it = replacements_.find(DICOM_TAG_DEIDENTIFICATION_METHOD);
 
     if (it != replacements_.end() &&
-        it->second == ORTHANC_DEIDENTIFICATION_METHOD)
+        it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD)
     {
+      delete it->second;
       replacements_.erase(it);
     }
   }
@@ -100,7 +143,7 @@
 
     dicom.Replace(*tag, mapped);
   }
-
+  
   DicomModification::DicomModification()
   {
     removePrivateTags_ = false;
@@ -108,10 +151,15 @@
     allowManualIdentifiers_ = true;
   }
 
+  DicomModification::~DicomModification()
+  {
+    ClearReplacements();
+  }
+
   void DicomModification::Keep(const DicomTag& tag)
   {
     removals_.erase(tag);
-    replacements_.erase(tag);
+    RemoveInternal(tag);
 
     if (FromDcmtkBridge::IsPrivateTag(tag))
     {
@@ -124,7 +172,7 @@
   void DicomModification::Remove(const DicomTag& tag)
   {
     removals_.insert(tag);
-    replacements_.erase(tag);
+    RemoveInternal(tag);
     privateTagsToKeep_.erase(tag);
 
     MarkNotOrthancAnonymization();
@@ -136,12 +184,12 @@
   }
 
   void DicomModification::Replace(const DicomTag& tag,
-                                  const std::string& value,
+                                  const Json::Value& value,
                                   bool safeForAnonymization)
   {
     removals_.erase(tag);
     privateTagsToKeep_.erase(tag);
-    replacements_[tag] = value;
+    ReplaceInternal(tag, value);
 
     if (!safeForAnonymization)
     {
@@ -149,12 +197,13 @@
     }
   }
 
+
   bool DicomModification::IsReplaced(const DicomTag& tag) const
   {
     return replacements_.find(tag) != replacements_.end();
   }
 
-  const std::string& DicomModification::GetReplacement(const DicomTag& tag) const
+  const Json::Value& DicomModification::GetReplacement(const DicomTag& tag) const
   {
     Replacements::const_iterator it = replacements_.find(tag);
 
@@ -164,10 +213,26 @@
     }
     else
     {
-      return it->second;
+      return *it->second;
     } 
   }
 
+
+  std::string DicomModification::GetReplacementAsString(const DicomTag& tag) const
+  {
+    const Json::Value& json = GetReplacement(tag);
+
+    if (json.type() != Json::stringValue)
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+    else
+    {
+      return json.asString();
+    }    
+  }
+
+
   void DicomModification::SetRemovePrivateTags(bool removed)
   {
     removePrivateTags_ = removed;
@@ -192,7 +257,7 @@
   void DicomModification::SetupAnonymization()
   {
     removals_.clear();
-    replacements_.clear();
+    ClearReplacements();
     removePrivateTags_ = true;
     level_ = ResourceType_Patient;
     uidMap_.clear();
@@ -255,15 +320,15 @@
     removals_.insert(DicomTag(0x0010, 0x2000));  // Medical Alerts
 
     // Set the DeidentificationMethod tag
-    replacements_.insert(std::make_pair(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD));
+    ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD);
 
     // Set the PatientIdentityRemoved tag
-    replacements_.insert(std::make_pair(DicomTag(0x0012, 0x0062), "YES"));
+    ReplaceInternal(DicomTag(0x0012, 0x0062), "YES");
 
     // (*) Choose a random patient name and ID
     std::string patientId = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient);
-    replacements_[DICOM_TAG_PATIENT_ID] = patientId;
-    replacements_[DICOM_TAG_PATIENT_NAME] = patientId;
+    ReplaceInternal(DICOM_TAG_PATIENT_ID, patientId);
+    ReplaceInternal(DICOM_TAG_PATIENT_NAME, patientId);
   }
 
   void DicomModification::Apply(ParsedDicomFile& toModify)
@@ -394,7 +459,7 @@
     for (Replacements::const_iterator it = replacements_.begin(); 
          it != replacements_.end(); ++it)
     {
-      toModify.Replace(it->first, it->second, DicomReplaceMode_InsertIfAbsent);
+      toModify.Replace(it->first, *it->second, DicomReplaceMode_InsertIfAbsent);
     }
 
     // (4) Update the DICOM identifiers
--- a/OrthancServer/DicomModification.h	Tue Oct 06 14:44:52 2015 +0200
+++ b/OrthancServer/DicomModification.h	Mon Oct 12 14:47:58 2015 +0200
@@ -36,7 +36,7 @@
 
 namespace Orthanc
 {
-  class DicomModification
+  class DicomModification : public boost::noncopyable
   {
     /**
      * Process:
@@ -47,7 +47,7 @@
 
   private:
     typedef std::set<DicomTag> SetOfTags;
-    typedef std::map<DicomTag, std::string> Replacements;
+    typedef std::map<DicomTag, Json::Value*> Replacements;
     typedef std::map< std::pair<ResourceType, std::string>, std::string>  UidMap;
 
     SetOfTags removals_;
@@ -63,9 +63,18 @@
 
     void MarkNotOrthancAnonymization();
 
+    void ClearReplacements();
+
+    void RemoveInternal(const DicomTag& tag);
+
+    void ReplaceInternal(const DicomTag& tag,
+                         const Json::Value& value);
+
   public:
     DicomModification();
 
+    ~DicomModification();
+
     void Keep(const DicomTag& tag);
 
     void Remove(const DicomTag& tag);
@@ -73,12 +82,14 @@
     bool IsRemoved(const DicomTag& tag) const;
 
     void Replace(const DicomTag& tag,
-                 const std::string& value,
+                 const Json::Value& value,   // Encoded using UTF-8
                  bool safeForAnonymization = false);
 
     bool IsReplaced(const DicomTag& tag) const;
 
-    const std::string& GetReplacement(const DicomTag& tag) const;
+    const Json::Value& GetReplacement(const DicomTag& tag) const;
+
+    std::string GetReplacementAsString(const DicomTag& tag) const;
 
     void SetRemovePrivateTags(bool removed);
 
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Tue Oct 06 14:44:52 2015 +0200
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -272,10 +272,22 @@
     const std::string syntax(xfer.getXferID());
     bool isGeneric = IsGenericTransferSyntax(syntax);
 
-    if (isGeneric ^ IsGenericTransferSyntax(connection.GetPreferredTransferSyntax()))
+    bool renegociate;
+    if (isGeneric)
     {
-      // Making a generic-to-specific or specific-to-generic change of
-      // the transfer syntax. Renegotiate the connection.
+      // Are we making a generic-to-specific or specific-to-generic change of
+      // the transfer syntax? If this is the case, renegotiate the connection.
+      renegociate = !IsGenericTransferSyntax(connection.GetPreferredTransferSyntax());
+    }
+    else
+    {
+      // We are using a specific transfer syntax. Renegociate if the
+      // current connection does not match this transfer syntax.
+      renegociate = (syntax != connection.GetPreferredTransferSyntax());
+    }
+
+    if (renegociate)
+    {
       LOG(INFO) << "Change in the transfer syntax: the C-Store associated must be renegotiated";
 
       if (isGeneric)
--- a/OrthancServer/FromDcmtkBridge.cpp	Tue Oct 06 14:44:52 2015 +0200
+++ b/OrthancServer/FromDcmtkBridge.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -95,6 +95,7 @@
 #include <dcmtk/dcmnet/dul.h>
 
 #include <boost/math/special_functions/round.hpp>
+#include <boost/algorithm/string/predicate.hpp>
 #include <dcmtk/dcmdata/dcostrmb.h>
 
 
@@ -349,19 +350,6 @@
   }
 
 
-  bool FromDcmtkBridge::IsPrivateTag(DcmTag& tag)
-  {
-#if 1
-    DcmTagKey tmp(tag.getGTag(), tag.getETag());
-    return tmp.isPrivate();
-#else
-    // Implementation for Orthanc versions <= 0.8.5
-    return (tag.getPrivateCreator() != NULL ||
-            !strcmp("PrivateCreator", tag.getTagName()));  // TODO - This may change with future versions of DCMTK
-#endif
-  }
-
-
   bool FromDcmtkBridge::IsPrivateTag(const DicomTag& tag)
   {
 #if 1
@@ -575,127 +563,190 @@
   }
 
 
-  static void StoreElement(Json::Value& target,
-                           DcmElement& element,
-                           unsigned int maxStringLength,
-                           Encoding encoding);
-
-  static void StoreItem(Json::Value& target,
-                        DcmItem& item,
-                        unsigned int maxStringLength,
-                        Encoding encoding)
+  static Json::Value& PrepareNode(Json::Value& parent,
+                                  DcmElement& element,
+                                  DicomToJsonFormat format)
   {
-    target = Json::Value(Json::objectValue);
-
-    for (unsigned long i = 0; i < item.card(); i++)
-    {
-      DcmElement* element = item.getElement(i);
-      StoreElement(target, *element, maxStringLength, encoding);
-    }
-  }
-
-
-  static void StoreElement(Json::Value& target,
-                           DcmElement& element,
-                           unsigned int maxStringLength,
-                           Encoding encoding)
-  {
-    assert(target.type() == Json::objectValue);
+    assert(parent.type() == Json::objectValue);
 
     DicomTag tag(FromDcmtkBridge::GetTag(element));
     const std::string formattedTag = tag.Format();
 
-#if 0
-    const std::string tagName = FromDcmtkBridge::GetName(tag);
-#else
-    // This version of the code gives access to the name of the private tags
+    if (format == DicomToJsonFormat_Short)
+    {
+      parent[formattedTag] = Json::nullValue;
+      return parent[formattedTag];
+    }
+
+    // This code gives access to the name of the private tags
     DcmTag tagbis(element.getTag());
     const std::string tagName(tagbis.getTagName());      
-#endif
+    
+    switch (format)
+    {
+      case DicomToJsonFormat_Simple:
+        parent[tagName] = Json::nullValue;
+        return parent[tagName];
+
+      case DicomToJsonFormat_Full:
+      {
+        parent[formattedTag] = Json::objectValue;
+        Json::Value& node = parent[formattedTag];
+
+        if (element.isLeaf())
+        {
+          node["Name"] = tagName;
+
+          if (tagbis.getPrivateCreator() != NULL)
+          {
+            node["PrivateCreator"] = tagbis.getPrivateCreator();
+          }
+
+          return node;
+        }
+        else
+        {
+          node["Name"] = tagName;
+          node["Type"] = "Sequence";
+          node["Value"] = Json::nullValue;
+          return node["Value"];
+        }
+      }
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  static void LeafValueToJson(Json::Value& target,
+                              const DicomValue& value,
+                              DicomToJsonFormat format,
+                              unsigned int maxStringLength)
+  {
+    std::string content = value.AsString();
+
+    switch (format)
+    {
+      case DicomToJsonFormat_Short:
+      case DicomToJsonFormat_Simple:
+      {
+        assert(target.type() == Json::nullValue);
+
+        if (!value.IsNull() &&
+            (maxStringLength == 0 ||
+             content.size() <= maxStringLength))
+        {
+          target = content;
+        }
+
+        break;
+      }      
+
+      case DicomToJsonFormat_Full:
+      {
+        assert(target.type() == Json::objectValue);
+
+        if (value.IsNull())
+        {
+          target["Type"] = "Null";
+          target["Value"] = Json::nullValue;
+        }
+        else
+        {
+          if (maxStringLength == 0 ||
+              content.size() <= maxStringLength)
+          {
+            target["Type"] = "String";
+            target["Value"] = content;
+          }
+          else
+          {
+            target["Type"] = "TooLong";
+            target["Value"] = Json::nullValue;
+          }
+        }
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }                              
+
+
+  static void DatasetToJson(Json::Value& parent,
+                            DcmItem& item,
+                            DicomToJsonFormat format,
+                            unsigned int maxStringLength,
+                            Encoding encoding);
+
+
+  void FromDcmtkBridge::ToJson(Json::Value& parent,
+                               DcmElement& element,
+                               DicomToJsonFormat format,
+                               unsigned int maxStringLength,
+                               Encoding encoding)
+  {
+    if (parent.type() == Json::nullValue)
+    {
+      parent = Json::objectValue;
+    }
+
+    assert(parent.type() == Json::objectValue);
+    Json::Value& target = PrepareNode(parent, element, format);
 
     if (element.isLeaf())
     {
-      Json::Value value(Json::objectValue);
-      value["Name"] = tagName;
-
-      if (tagbis.getPrivateCreator() != NULL)
-      {
-        value["PrivateCreator"] = tagbis.getPrivateCreator();
-      }
-
       std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element, encoding));
-      if (v->IsNull())
-      {
-        value["Type"] = "Null";
-        value["Value"] = Json::nullValue;
-      }
-      else
-      {
-        std::string s = v->AsString();
-        if (maxStringLength == 0 ||
-            s.size() <= maxStringLength)
-        {
-          value["Type"] = "String";
-          value["Value"] = s;
-        }
-        else
-        {
-          value["Type"] = "TooLong";
-          value["Value"] = Json::nullValue;
-        }
-      }
-
-      target[formattedTag] = value;
+      LeafValueToJson(target, *v, format, maxStringLength);
     }
     else
     {
-      Json::Value children(Json::arrayValue);
+      assert(target.type() == Json::nullValue);
+      target = Json::arrayValue;
 
       // "All subclasses of DcmElement except for DcmSequenceOfItems
       // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset
-      // etc. are not." The following cast is thus OK.
+      // etc. are not." The following dynamic_cast is thus OK.
       DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(element);
 
       for (unsigned long i = 0; i < sequence.card(); i++)
       {
         DcmItem* child = sequence.getItem(i);
-        Json::Value& v = children.append(Json::objectValue);
-        StoreItem(v, *child, maxStringLength, encoding);
-      }  
-
-      target[formattedTag]["Name"] = tagName;
-      target[formattedTag]["Type"] = "Sequence";
-      target[formattedTag]["Value"] = children;
+        Json::Value& v = target.append(Json::objectValue);
+        DatasetToJson(v, *child, format, maxStringLength, encoding);
+      }
     }
   }
 
 
-  void FromDcmtkBridge::ToJson(Json::Value& root, 
-                               DcmDataset& dataset,
-                               unsigned int maxStringLength)
+  static void DatasetToJson(Json::Value& parent,
+                            DcmItem& item,
+                            DicomToJsonFormat format,
+                            unsigned int maxStringLength,
+                            Encoding encoding)
   {
-    StoreItem(root, dataset, maxStringLength, DetectEncoding(dataset));
+    assert(parent.type() == Json::objectValue);
+
+    for (unsigned long i = 0; i < item.card(); i++)
+    {
+      DcmElement* element = item.getElement(i);
+      FromDcmtkBridge::ToJson(parent, *element, format, maxStringLength, encoding);
+    }
   }
 
 
-
   void FromDcmtkBridge::ToJson(Json::Value& target, 
-                               const std::string& path,
+                               DcmDataset& dataset,
+                               DicomToJsonFormat format,
                                unsigned int maxStringLength)
   {
-    DcmFileFormat dicom;
-    if (!dicom.loadFile(path.c_str()).good())
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-    else
-    {
-      FromDcmtkBridge::ToJson(target, *dicom.getDataset(), maxStringLength);
-    }
+    target = Json::objectValue;
+    DatasetToJson(target, dataset, format, maxStringLength, DetectEncoding(dataset));
   }
 
 
-
   std::string FromDcmtkBridge::GetName(const DicomTag& t)
   {
     // Some patches for important tags because of different DICOM
@@ -947,4 +998,369 @@
     }
   }
 
+
+  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
+
+      /**
+       * TODO.
+       **/
+    
+      case EVR_OB:  // other byte
+      case EVR_OF:  // other float
+      case EVR_OW:  // other word
+      case EVR_AT:  // attribute tag
+        throw OrthancException(ErrorCode_NotImplemented);
+
+      case EVR_UN:  // unknown value representation
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+
+
+      /**
+       * 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);
+
+        
+      /**
+       * 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);
+
+
+      /**
+       * Internal to DCMTK.
+       **/ 
+
+      case EVR_ox:  // OB or OW depending on context
+      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);          
+  }
+
+
+
+  void FromDcmtkBridge::FillElementWithString(DcmElement& element,
+                                              const DicomTag& tag,
+                                              const std::string& utf8Value,
+                                              bool decodeBinaryTags,
+                                              Encoding dicomEncoding)
+  {
+    std::string binary;
+    const std::string* decoded = &utf8Value;
+
+    if (decodeBinaryTags &&
+        boost::starts_with(utf8Value, "data:application/octet-stream;base64,"))
+    {
+      std::string mime;
+      Toolbox::DecodeDataUriScheme(mime, binary, utf8Value);
+      decoded = &binary;
+    }
+    else if (dicomEncoding != Encoding_Utf8)
+    {
+      binary = Toolbox::ConvertFromUtf8(utf8Value, dicomEncoding);
+      decoded = &binary;
+    }
+
+    DcmTag key(tag.GetGroup(), tag.GetElement());
+
+    if (IsBinaryTag(key))
+    {
+      if (element.putUint8Array((const Uint8*) decoded->c_str(), decoded->size()).good())
+      {
+        return;
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+
+    bool ok = false;
+    
+    try
+    {
+      switch (key.getEVR())
+      {
+        // http://support.dcmtk.org/docs/dcvr_8h-source.html
+
+        /**
+         * TODO.
+         **/
+
+        case EVR_OB:  // other byte
+        case EVR_OF:  // other float
+        case EVR_OW:  // other word
+        case EVR_AT:  // attribute tag
+          throw OrthancException(ErrorCode_NotImplemented);
+    
+        case EVR_UN:  // unknown value representation
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+
+
+        /**
+         * String types.
+         **/
+      
+        case EVR_DS:  // decimal string
+        case EVR_IS:  // integer string
+        case EVR_AS:  // age string
+        case EVR_DA:  // date string
+        case EVR_DT:  // date time string
+        case EVR_TM:  // time string
+        case EVR_AE:  // application entity title
+        case EVR_CS:  // code string
+        case EVR_SH:  // short string
+        case EVR_LO:  // long string
+        case EVR_ST:  // short text
+        case EVR_LT:  // long text
+        case EVR_UT:  // unlimited text
+        case EVR_PN:  // person name
+        case EVR_UI:  // unique identifier
+        {
+          ok = element.putString(decoded->c_str()).good();
+          break;
+        }
+
+        
+        /**
+         * Numerical types
+         **/ 
+      
+        case EVR_SL:  // signed long
+        {
+          ok = element.putSint32(boost::lexical_cast<Sint32>(*decoded)).good();
+          break;
+        }
+
+        case EVR_SS:  // signed short
+        {
+          ok = element.putSint16(boost::lexical_cast<Sint16>(*decoded)).good();
+          break;
+        }
+
+        case EVR_UL:  // unsigned long
+        {
+          ok = element.putUint32(boost::lexical_cast<Uint32>(*decoded)).good();
+          break;
+        }
+
+        case EVR_US:  // unsigned short
+        {
+          ok = element.putUint16(boost::lexical_cast<Uint16>(*decoded)).good();
+          break;
+        }
+
+        case EVR_FL:  // float single-precision
+        {
+          ok = element.putFloat32(boost::lexical_cast<float>(*decoded)).good();
+          break;
+        }
+
+        case EVR_FD:  // float double-precision
+        {
+          ok = element.putFloat64(boost::lexical_cast<double>(*decoded)).good();
+          break;
+        }
+
+
+        /**
+         * Sequence types, should never occur at this point.
+         **/
+
+        case EVR_SQ:  // sequence of items
+        {
+          ok = false;
+          break;
+        }
+
+
+        /**
+         * Internal to DCMTK.
+         **/ 
+
+        case EVR_ox:  // OB or OW depending on context
+        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;
+      }
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      ok = false;
+    }
+
+    if (!ok)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
+  DcmElement* FromDcmtkBridge::FromJson(const DicomTag& tag,
+                                        const Json::Value& value,
+                                        bool decodeBinaryTags,
+                                        Encoding dicomEncoding)
+  {
+    std::auto_ptr<DcmElement> element;
+
+    switch (value.type())
+    {
+      case Json::stringValue:
+        element.reset(CreateElementForTag(tag));
+        FillElementWithString(*element, tag, value.asString(), decodeBinaryTags, dicomEncoding);
+        break;
+
+      case Json::arrayValue:
+      {
+        DcmTag key(tag.GetGroup(), tag.GetElement());
+        if (key.getEVR() != EVR_SQ)
+        {
+          throw OrthancException(ErrorCode_BadParameterType);
+        }
+
+        DcmSequenceOfItems* sequence = new DcmSequenceOfItems(key, value.size());
+        element.reset(sequence);
+        
+        for (Json::Value::ArrayIndex i = 0; i < value.size(); i++)
+        {
+          std::auto_ptr<DcmItem> item(new DcmItem);
+
+          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]], decodeBinaryTags, dicomEncoding));
+          }
+
+          sequence->append(item.release());
+        }
+
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_BadParameterType);
+    }
+
+    return element.release();
+  }
 }
--- a/OrthancServer/FromDcmtkBridge.h	Tue Oct 06 14:44:52 2015 +0200
+++ b/OrthancServer/FromDcmtkBridge.h	Mon Oct 12 14:47:58 2015 +0200
@@ -60,8 +60,6 @@
 
     static DicomTag GetTag(const DcmElement& element);
 
-    static bool IsPrivateTag(DcmTag& tag);
-
     static bool IsPrivateTag(const DicomTag& tag);
 
     static bool IsUnknownTag(const DicomTag& tag);
@@ -69,13 +67,16 @@
     static DicomValue* ConvertLeafElement(DcmElement& element,
                                           Encoding encoding);
 
+    static void ToJson(Json::Value& parent,
+                       DcmElement& element,
+                       DicomToJsonFormat format,
+                       unsigned int maxStringLength,
+                       Encoding encoding);
+
     static void ToJson(Json::Value& target, 
                        DcmDataset& dataset,
-                       unsigned int maxStringLength = 256);       
-
-    static void ToJson(Json::Value& target, 
-                       const std::string& path,
-                       unsigned int maxStringLength = 256);
+                       DicomToJsonFormat format,
+                       unsigned int maxStringLength);
 
     static std::string GetName(const DicomTag& tag);
 
@@ -118,5 +119,18 @@
                                    DcmDataset& dataSet);
 
     static ValueRepresentation GetValueRepresentation(const DicomTag& tag);
+
+    static DcmElement* CreateElementForTag(const DicomTag& tag);
+    
+    static void FillElementWithString(DcmElement& element,
+                                      const DicomTag& tag,
+                                      const std::string& utf8alue,  // Encoded using UTF-8
+                                      bool interpretBinaryTags,
+                                      Encoding dicomEncoding);
+
+    static DcmElement* FromJson(const DicomTag& tag,
+                                const Json::Value& element,  // Encoding using UTF-8
+                                bool interpretBinaryTags,
+                                Encoding dicomEncoding);
   };
 }
--- a/OrthancServer/Internals/StoreScp.cpp	Tue Oct 06 14:44:52 2015 +0200
+++ b/OrthancServer/Internals/StoreScp.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -168,7 +168,8 @@
           try
           {
             FromDcmtkBridge::Convert(summary, **imageDataSet);
-            FromDcmtkBridge::ToJson(dicomJson, **imageDataSet);       
+            FromDcmtkBridge::ToJson(dicomJson, **imageDataSet,
+                                    DicomToJsonFormat_Full, 256 /* max string length */);
 
             if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, **imageDataSet))
             {
--- a/OrthancServer/LuaScripting.cpp	Tue Oct 06 14:44:52 2015 +0200
+++ b/OrthancServer/LuaScripting.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -270,10 +270,12 @@
     if (operation == "modify")
     {
       LOG(INFO) << "Lua script to modify resource " << parameters["Resource"].asString();
-      DicomModification modification;
-      OrthancRestApi::ParseModifyRequest(modification, parameters);
+      std::auto_ptr<DicomModification> modification(new DicomModification);
+      OrthancRestApi::ParseModifyRequest(*modification, parameters);
 
-      std::auto_ptr<ModifyInstanceCommand> command(new ModifyInstanceCommand(context_, RequestOrigin_Lua, modification));
+      std::auto_ptr<ModifyInstanceCommand> command
+        (new ModifyInstanceCommand(context_, RequestOrigin_Lua, modification.release()));
+
       return command.release();
     }
 
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Tue Oct 06 14:44:52 2015 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -98,12 +98,13 @@
     for (size_t i = 0; i < members.size(); i++)
     {
       const std::string& name = members[i];
-      std::string value = replacements[name].asString();
+      const Json::Value& value = replacements[name];
 
       DicomTag tag = FromDcmtkBridge::ParseTag(name);
       target.Replace(tag, value);
 
-      VLOG(1) << "Replace: " << name << " " << tag << " == " << value << std::endl;
+      VLOG(1) << "Replace: " << name << " " << tag 
+              << " == " << value.toStyledString() << std::endl;
     }
   }
 
@@ -168,7 +169,7 @@
     // curl http://localhost:8042/instances/6e67da51-d119d6ae-c5667437-87b9a8a5-0f07c49f/anonymize -X POST -d '{"Replace":{"PatientName":"hello","0010-0020":"world"},"Keep":["StudyDescription", "SeriesDescription"],"KeepPrivateTags": null,"Remove":["Modality"]}' > Anonymized.dcm
 
     target.SetupAnonymization();
-    std::string patientName = target.GetReplacement(DICOM_TAG_PATIENT_NAME);
+    std::string patientName = target.GetReplacementAsString(DICOM_TAG_PATIENT_NAME);
 
     Json::Value request;
     if (call.ParseJsonRequest(request) && request.isObject())
@@ -487,7 +488,7 @@
 
   static void InjectTags(ParsedDicomFile& dicom,
                          const Json::Value& tags,
-                         bool interpretBinaryTags)
+                         bool decodeBinaryTags)
   {
     if (tags.type() != Json::objectValue)
     {
@@ -499,12 +500,6 @@
     for (size_t i = 0; i < members.size(); i++)
     {
       const std::string& name = members[i];
-      if (tags[name].type() != Json::stringValue)
-      {
-        throw OrthancException(ErrorCode_CreateDicomNotString);
-      }
-
-      std::string value = tags[name].asString();
       DicomTag tag = FromDcmtkBridge::ParseTag(name);
 
       if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET)
@@ -529,16 +524,9 @@
         {
           throw OrthancException(ErrorCode_CreateDicomUseContent);
         }
-        else if (interpretBinaryTags &&
-                 boost::starts_with(value, "data:application/octet-stream;base64,"))
-        {
-          std::string mime, binary;
-          Toolbox::DecodeDataUriScheme(mime, binary, value);
-          dicom.Replace(tag, binary);
-        }
         else
         {
-          dicom.Replace(tag, Toolbox::ConvertFromUtf8(value, dicom.GetEncoding()));
+          dicom.Replace(tag, tags[name], decodeBinaryTags);
         }
       }
     }
@@ -548,7 +536,7 @@
   static void CreateSeries(RestApiPostCall& call,
                            ParsedDicomFile& base /* in */,
                            const Json::Value& content,
-                           bool interpretBinaryTags)
+                           bool decodeBinaryTags)
   {
     assert(content.isArray());
     assert(content.size() > 0);
@@ -581,7 +569,7 @@
 
           if (content[i].isMember("Tags"))
           {
-            InjectTags(*dicom, content[i]["Tags"], interpretBinaryTags);
+            InjectTags(*dicom, content[i]["Tags"], decodeBinaryTags);
           }
         }
 
@@ -755,7 +743,7 @@
     }
 
 
-    bool interpretBinaryTags = true;
+    bool decodeBinaryTags = true;
     if (request.isMember("InterpretBinaryTags"))
     {
       const Json::Value& v = request["InterpretBinaryTags"];
@@ -764,7 +752,7 @@
         throw OrthancException(ErrorCode_BadRequest);
       }
 
-      interpretBinaryTags = v.asBool();
+      decodeBinaryTags = v.asBool();
     }
 
     
@@ -794,7 +782,7 @@
     }
 
 
-    InjectTags(dicom, request["Tags"], interpretBinaryTags);
+    InjectTags(dicom, request["Tags"], decodeBinaryTags);
 
 
     // Inject the content (either an image, or a PDF file)
@@ -812,7 +800,7 @@
         if (content.size() > 0)
         {
           // Let's create a series instead of a single instance
-          CreateSeries(call, dicom, content, interpretBinaryTags);
+          CreateSeries(call, dicom, content, decodeBinaryTags);
           return;
         }
       }
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Tue Oct 06 14:44:52 2015 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -479,6 +479,7 @@
     {
       Json::Value operations = Json::arrayValue;
 
+      operations.append("compress");
       operations.append("compressed-data");
 
       if (info.GetCompressedMD5() != "")
@@ -488,6 +489,7 @@
 
       operations.append("compressed-size");
       operations.append("data");
+      operations.append("is-compressed");
 
       if (info.GetUncompressedMD5() != "")
       {
@@ -495,6 +497,7 @@
       }
 
       operations.append("size");
+      operations.append("uncompress");
 
       if (info.GetCompressedMD5() != "" &&
           info.GetUncompressedMD5() != "")
@@ -663,6 +666,31 @@
   }
 
 
+  template <enum CompressionType compression>
+  static void ChangeAttachmentCompression(RestApiPostCall& call)
+  {
+    CheckValidResourceType(call);
+
+    std::string publicId = call.GetUriComponent("id", "");
+    std::string name = call.GetUriComponent("name", "");
+    FileContentType contentType = StringToContentType(name);
+
+    OrthancRestApi::GetContext(call).ChangeAttachmentCompression(publicId, contentType, compression);
+    call.GetOutput().AnswerBuffer("{}", "application/json");
+  }
+
+
+  static void IsAttachmentCompressed(RestApiGetCall& call)
+  {
+    FileInfo info;
+    if (GetAttachmentInfo(info, call))
+    {
+      std::string answer = (info.GetCompressionType() == CompressionType_None) ? "0" : "1";
+      call.GetOutput().AnswerBuffer(answer, "text/plain");
+    }
+  }
+
+
   // Raw access to the DICOM tags of an instance ------------------------------
 
   static void GetRawContent(RestApiGetCall& call)
@@ -1125,14 +1153,17 @@
     Register("/{resourceType}/{id}/attachments", ListAttachments);
     Register("/{resourceType}/{id}/attachments/{name}", DeleteAttachment);
     Register("/{resourceType}/{id}/attachments/{name}", GetAttachmentOperations);
+    Register("/{resourceType}/{id}/attachments/{name}", UploadAttachment);
+    Register("/{resourceType}/{id}/attachments/{name}/compress", ChangeAttachmentCompression<CompressionType_ZlibWithSize>);
     Register("/{resourceType}/{id}/attachments/{name}/compressed-data", GetAttachmentData<0>);
     Register("/{resourceType}/{id}/attachments/{name}/compressed-md5", GetAttachmentCompressedMD5);
     Register("/{resourceType}/{id}/attachments/{name}/compressed-size", GetAttachmentCompressedSize);
     Register("/{resourceType}/{id}/attachments/{name}/data", GetAttachmentData<1>);
+    Register("/{resourceType}/{id}/attachments/{name}/is-compressed", IsAttachmentCompressed);
     Register("/{resourceType}/{id}/attachments/{name}/md5", GetAttachmentMD5);
     Register("/{resourceType}/{id}/attachments/{name}/size", GetAttachmentSize);
+    Register("/{resourceType}/{id}/attachments/{name}/uncompress", ChangeAttachmentCompression<CompressionType_None>);
     Register("/{resourceType}/{id}/attachments/{name}/verify-md5", VerifyAttachment);
-    Register("/{resourceType}/{id}/attachments/{name}", UploadAttachment);
 
     Register("/tools/lookup", Lookup);
     Register("/tools/find", Find);
--- a/OrthancServer/ParsedDicomFile.cpp	Tue Oct 06 14:44:52 2015 +0200
+++ b/OrthancServer/ParsedDicomFile.cpp	Mon Oct 12 14:47:58 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";
@@ -148,7 +149,6 @@
   struct ParsedDicomFile::PImpl
   {
     std::auto_ptr<DcmFileFormat> file_;
-    Encoding encoding_;
   };
 
 
@@ -173,8 +173,6 @@
     }
     pimpl_->file_->loadAllDataIntoMemory();
     pimpl_->file_->transferEnd();
-
-    pimpl_->encoding_ = FromDcmtkBridge::DetectEncoding(*pimpl_->file_->getDataset());
   }
 
 
@@ -519,284 +517,6 @@
   }
 
 
-  
-
-
-  static DcmElement* CreateElementForTag(const DicomTag& tag)
-  {
-    DcmTag key(tag.GetGroup(), tag.GetElement());
-
-    switch (key.getEVR())
-    {
-      // http://support.dcmtk.org/docs/dcvr_8h-source.html
-
-      /**
-       * TODO.
-       **/
-    
-      case EVR_OB:  // other byte
-      case EVR_OF:  // other float
-      case EVR_OW:  // other word
-      case EVR_AT:  // attribute tag
-        throw OrthancException(ErrorCode_NotImplemented);
-
-      case EVR_UN:  // unknown value representation
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-
-
-      /**
-       * 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);
-
-        
-      /**
-       * 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);
-
-
-      /**
-       * Internal to DCMTK.
-       **/ 
-
-      case EVR_ox:  // OB or OW depending on context
-      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);          
-  }
-
-
-
-  static void FillElementWithString(DcmElement& element,
-                                    const DicomTag& tag,
-                                    const std::string& value)
-  {
-    DcmTag key(tag.GetGroup(), tag.GetElement());
-    bool ok = false;
-    
-    try
-    {
-      switch (key.getEVR())
-      {
-        // http://support.dcmtk.org/docs/dcvr_8h-source.html
-
-        /**
-         * TODO.
-         **/
-
-        case EVR_OB:  // other byte
-        case EVR_OF:  // other float
-        case EVR_OW:  // other word
-        case EVR_AT:  // attribute tag
-          throw OrthancException(ErrorCode_NotImplemented);
-    
-        case EVR_UN:  // unknown value representation
-          throw OrthancException(ErrorCode_ParameterOutOfRange);
-
-
-        /**
-         * String types.
-         **/
-      
-        case EVR_DS:  // decimal string
-        case EVR_IS:  // integer string
-        case EVR_AS:  // age string
-        case EVR_DA:  // date string
-        case EVR_DT:  // date time string
-        case EVR_TM:  // time string
-        case EVR_AE:  // application entity title
-        case EVR_CS:  // code string
-        case EVR_SH:  // short string
-        case EVR_LO:  // long string
-        case EVR_ST:  // short text
-        case EVR_LT:  // long text
-        case EVR_UT:  // unlimited text
-        case EVR_PN:  // person name
-        case EVR_UI:  // unique identifier
-        {
-          ok = element.putString(value.c_str()).good();
-          break;
-        }
-
-        
-        /**
-         * Numerical types
-         **/ 
-      
-        case EVR_SL:  // signed long
-        {
-          ok = element.putSint32(boost::lexical_cast<Sint32>(value)).good();
-          break;
-        }
-
-        case EVR_SS:  // signed short
-        {
-          ok = element.putSint16(boost::lexical_cast<Sint16>(value)).good();
-          break;
-        }
-
-        case EVR_UL:  // unsigned long
-        {
-          ok = element.putUint32(boost::lexical_cast<Uint32>(value)).good();
-          break;
-        }
-
-        case EVR_US:  // unsigned short
-        {
-          ok = element.putUint16(boost::lexical_cast<Uint16>(value)).good();
-          break;
-        }
-
-        case EVR_FL:  // float single-precision
-        {
-          ok = element.putFloat32(boost::lexical_cast<float>(value)).good();
-          break;
-        }
-
-        case EVR_FD:  // float double-precision
-        {
-          ok = element.putFloat64(boost::lexical_cast<double>(value)).good();
-          break;
-        }
-
-
-        /**
-         * Sequence types, should never occur at this point.
-         **/
-
-        case EVR_SQ:  // sequence of items
-        {
-          ok = false;
-          break;
-        }
-
-
-        /**
-         * Internal to DCMTK.
-         **/ 
-
-        case EVR_ox:  // OB or OW depending on context
-        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;
-      }
-    }
-    catch (boost::bad_lexical_cast&)
-    {
-      ok = false;
-    }
-
-    if (!ok)
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }
-  }
-
-
   void ParsedDicomFile::Remove(const DicomTag& tag)
   {
     DcmTagKey key(tag.GetGroup(), tag.GetElement());
@@ -823,7 +543,7 @@
       DcmTag tag(element->getTag());
 
       // Is this a private tag?
-      if (FromDcmtkBridge::IsPrivateTag(tag))
+      if (tag.isPrivate())
       {
         bool remove = true;
 
@@ -857,54 +577,40 @@
   }
 
 
-
-
-  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(CreateElementForTag(tag));
-      FillElementWithString(*element, tag, value);
-
-      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 Json::Value& value,
+                               bool decodeBinaryTags)
   {
-    DcmTagKey key(tag.GetGroup(), tag.GetElement());
-    DcmElement* element = NULL;
+    std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeBinaryTags, GetEncoding()));
+    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:
@@ -914,23 +620,43 @@
           return;
       }
     }
+
+    // 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& utf8Value,
+                                         bool decodeBinaryTags)
+  {
+    if (tag != DICOM_TAG_SOP_CLASS_UID &&
+        tag != DICOM_TAG_SOP_INSTANCE_UID)
+    {
+      return;
+    }
+
+    std::string binary;
+    const std::string* decoded = &utf8Value;
+
+    if (decodeBinaryTags &&
+        boost::starts_with(utf8Value, "data:application/octet-stream;base64,"))
+    {
+      std::string mime;
+      Toolbox::DecodeDataUriScheme(mime, binary, utf8Value);
+      decoded = &binary;
+    }
     else
     {
-      if (FromDcmtkBridge::IsPrivateTag(tag) ||
-          FromDcmtkBridge::IsUnknownTag(tag))
+      Encoding encoding = GetEncoding();
+      if (GetEncoding() != Encoding_Utf8)
       {
-        if (!element->putUint8Array((const Uint8*) value.c_str(), value.size()).good())
-        {
-          throw OrthancException(ErrorCode_InternalError);
-        }
-      }
-      else
-      {
-        FillElementWithString(*element, tag, value);
+        binary = Toolbox::ConvertFromUtf8(utf8Value, encoding);
+        decoded = &binary;
       }
     }
 
-
     /**
      * dcmodify will automatically correct 'Media Storage SOP Class
      * UID' and 'Media Storage SOP Instance UID' in the metaheader, if
@@ -942,12 +668,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& utf8Value,
+                                DicomReplaceMode mode)
+  {
+    std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag));
+    FromDcmtkBridge::FillElementWithString(*element, tag, utf8Value, false, GetEncoding());
+    ReplaceInternal(*pimpl_->file_->getDataset(), element, mode);
+    UpdateStorageUid(tag, utf8Value, 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, GetEncoding()));
+    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);
     }
   }
 
@@ -1005,7 +763,7 @@
         return false;
       }
 
-      std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(*element, pimpl_->encoding_));
+      std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(*element, GetEncoding()));
       
       if (v.get() == NULL)
       {
@@ -1085,7 +843,6 @@
   ParsedDicomFile::ParsedDicomFile() : pimpl_(new PImpl)
   {
     pimpl_->file_.reset(new DcmFileFormat);
-    pimpl_->encoding_ = Encoding_Ascii;
     Replace(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient));
     Replace(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study));
     Replace(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series));
@@ -1115,7 +872,6 @@
     pimpl_(new PImpl)
   {
     pimpl_->file_.reset(dynamic_cast<DcmFileFormat*>(other.pimpl_->file_->clone()));
-    pimpl_->encoding_ = other.pimpl_->encoding_;
 
     // Create a new instance-level identifier
     Replace(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
@@ -1156,7 +912,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);
     }
   }
@@ -1345,7 +1101,7 @@
 
   Encoding ParsedDicomFile::GetEncoding() const
   {
-    return pimpl_->encoding_;
+    return FromDcmtkBridge::DetectEncoding(*pimpl_->file_->getDataset());
   }
 
 
@@ -1358,24 +1114,15 @@
       return;
     }
 
-    pimpl_->encoding_ = encoding;
-
     std::string s = GetDicomSpecificCharacterSet(encoding);
     Replace(DICOM_TAG_SPECIFIC_CHARACTER_SET, s, DicomReplaceMode_InsertIfAbsent);
   }
 
-  void ParsedDicomFile::ToJson(Json::Value& target, bool simplify)
+  void ParsedDicomFile::ToJson(Json::Value& target, 
+                               DicomToJsonFormat format,
+                               unsigned int maxStringLength)
   {
-    if (simplify)
-    {
-      Json::Value tmp;
-      FromDcmtkBridge::ToJson(tmp, *pimpl_->file_->getDataset());
-      Toolbox::SimplifyTags(target, tmp);
-    }
-    else
-    {
-      FromDcmtkBridge::ToJson(target, *pimpl_->file_->getDataset());
-    }
+    FromDcmtkBridge::ToJson(target, *pimpl_->file_->getDataset(), format, maxStringLength);
   }
 
 
--- a/OrthancServer/ParsedDicomFile.h	Tue Oct 06 14:44:52 2015 +0200
+++ b/OrthancServer/ParsedDicomFile.h	Mon Oct 12 14:47:58 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
 
@@ -74,13 +78,19 @@
 
     void Remove(const DicomTag& tag);
 
-    void Insert(const DicomTag& tag,
-                const std::string& value);
+    void Replace(const DicomTag& tag,
+                 const std::string& utf8Value,
+                 DicomReplaceMode mode = DicomReplaceMode_InsertIfAbsent);
 
     void Replace(const DicomTag& tag,
-                 const std::string& value,
+                 const Json::Value& value,  // Assumed to be encoded with UTF-8
+                 bool decodeBinaryTags,
                  DicomReplaceMode mode = DicomReplaceMode_InsertIfAbsent);
 
+    void Insert(const DicomTag& tag,
+                const Json::Value& value,   // Assumed to be encoded with UTF-8
+                bool decodeBinaryTags);
+
     void RemovePrivateTags()
     {
       RemovePrivateTagsInternal(NULL);
@@ -123,7 +133,8 @@
     void SetEncoding(Encoding encoding);
 
     void ToJson(Json::Value& target, 
-                bool simplify);
+                DicomToJsonFormat format,
+                unsigned int maxStringLength);
 
     bool HasTag(const DicomTag& tag) const;
 
--- a/OrthancServer/Scheduler/ModifyInstanceCommand.cpp	Tue Oct 06 14:44:52 2015 +0200
+++ b/OrthancServer/Scheduler/ModifyInstanceCommand.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -39,28 +39,28 @@
 {
   ModifyInstanceCommand::ModifyInstanceCommand(ServerContext& context,
                                                RequestOrigin origin,
-                                               const DicomModification& modification) :
+                                               DicomModification* modification) :
     context_(context),
     origin_(origin),
     modification_(modification)
   {
-    modification_.SetAllowManualIdentifiers(true);
+    modification_->SetAllowManualIdentifiers(true);
 
-    if (modification_.IsReplaced(DICOM_TAG_PATIENT_ID))
+    if (modification_->IsReplaced(DICOM_TAG_PATIENT_ID))
     {
-      modification_.SetLevel(ResourceType_Patient);
+      modification_->SetLevel(ResourceType_Patient);
     }
-    else if (modification_.IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
+    else if (modification_->IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
     {
-      modification_.SetLevel(ResourceType_Study);
+      modification_->SetLevel(ResourceType_Study);
     }
-    else if (modification_.IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
+    else if (modification_->IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
     {
-      modification_.SetLevel(ResourceType_Series);
+      modification_->SetLevel(ResourceType_Series);
     }
     else
     {
-      modification_.SetLevel(ResourceType_Instance);
+      modification_->SetLevel(ResourceType_Instance);
     }
 
     if (origin_ != RequestOrigin_Lua)
@@ -71,6 +71,15 @@
   }
 
 
+  ModifyInstanceCommand::~ModifyInstanceCommand()
+  {
+    if (modification_)
+    {
+      delete modification_;
+    }
+  }
+
+
   bool ModifyInstanceCommand::Apply(ListOfStrings& outputs,
                                     const ListOfStrings& inputs)
   {
@@ -88,7 +97,7 @@
           modified.reset(lock.GetDicom().Clone());
         }
 
-        modification_.Apply(*modified);
+        modification_->Apply(*modified);
 
         DicomInstanceToStore toStore;
         assert(origin_ == RequestOrigin_Lua);
--- a/OrthancServer/Scheduler/ModifyInstanceCommand.h	Tue Oct 06 14:44:52 2015 +0200
+++ b/OrthancServer/Scheduler/ModifyInstanceCommand.h	Mon Oct 12 14:47:58 2015 +0200
@@ -43,16 +43,18 @@
   private:
     ServerContext& context_;
     RequestOrigin origin_;
-    DicomModification modification_;
+    DicomModification* modification_;
 
   public:
     ModifyInstanceCommand(ServerContext& context,
                           RequestOrigin origin,
-                          const DicomModification& modification);
+                          DicomModification* modification);  // takes the ownership
+
+    virtual ~ModifyInstanceCommand();
 
     const DicomModification& GetModification() const
     {
-      return modification_;
+      return *modification_;
     }
 
     virtual bool Apply(ListOfStrings& outputs,
--- a/OrthancServer/ServerContext.cpp	Tue Oct 06 14:44:52 2015 +0200
+++ b/OrthancServer/ServerContext.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -307,15 +307,14 @@
   }
 
 
-
   void ServerContext::AnswerAttachment(RestApiOutput& output,
-                                       const std::string& instancePublicId,
+                                       const std::string& resourceId,
                                        FileContentType content)
   {
     FileInfo attachment;
-    if (!index_.LookupAttachment(attachment, instancePublicId, content))
+    if (!index_.LookupAttachment(attachment, resourceId, content))
     {
-      throw OrthancException(ErrorCode_InternalError);
+      throw OrthancException(ErrorCode_UnknownResource);
     }
 
     StorageAccessor accessor(area_);
@@ -323,6 +322,52 @@
   }
 
 
+  void ServerContext::ChangeAttachmentCompression(const std::string& resourceId,
+                                                  FileContentType attachmentType,
+                                                  CompressionType compression)
+  {
+    LOG(INFO) << "Changing compression type for attachment "
+              << EnumerationToString(attachmentType) 
+              << " of resource " << resourceId << " to " 
+              << compression; 
+
+    FileInfo attachment;
+    if (!index_.LookupAttachment(attachment, resourceId, attachmentType))
+    {
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+
+    if (attachment.GetCompressionType() == compression)
+    {
+      // Nothing to do
+      return;
+    }
+
+    std::string content;
+
+    StorageAccessor accessor(area_);
+    accessor.Read(content, attachment);
+
+    FileInfo modified = accessor.Write(content.empty() ? NULL : content.c_str(),
+                                       content.size(), attachmentType, compression, storeMD5_);
+
+    try
+    {
+      StoreStatus status = index_.AddAttachment(modified, resourceId);
+      if (status != StoreStatus_Success)
+      {
+        accessor.Remove(modified);
+        throw OrthancException(ErrorCode_Database);
+      }
+    }
+    catch (OrthancException&)
+    {
+      accessor.Remove(modified);
+      throw;
+    }    
+  }
+
+
   void ServerContext::ReadJson(Json::Value& result,
                                const std::string& instancePublicId)
   {
--- a/OrthancServer/ServerContext.h	Tue Oct 06 14:44:52 2015 +0200
+++ b/OrthancServer/ServerContext.h	Mon Oct 12 14:47:58 2015 +0200
@@ -184,9 +184,13 @@
                       DicomInstanceToStore& dicom);
 
     void AnswerAttachment(RestApiOutput& output,
-                          const std::string& instancePublicId,
+                          const std::string& resourceId,
                           FileContentType content);
 
+    void ChangeAttachmentCompression(const std::string& resourceId,
+                                     FileContentType attachmentType,
+                                     CompressionType compression);
+
     void ReadJson(Json::Value& result,
                   const std::string& instancePublicId);
 
--- a/OrthancServer/ServerEnumerations.h	Tue Oct 06 14:44:52 2015 +0200
+++ b/OrthancServer/ServerEnumerations.h	Mon Oct 12 14:47:58 2015 +0200
@@ -100,6 +100,13 @@
     ValueRepresentation_Time
   };
 
+  enum DicomToJsonFormat
+  {
+    DicomToJsonFormat_Full,
+    DicomToJsonFormat_Short,
+    DicomToJsonFormat_Simple
+  };
+
 
   /**
    * WARNING: Do not change the explicit values in the enumerations
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/Lua/ModifyInstanceWithSequence.lua	Mon Oct 12 14:47:58 2015 +0200
@@ -0,0 +1,28 @@
+-- Answer to:
+-- https://groups.google.com/d/msg/orthanc-users/0ymHe1cDBCQ/YfD0NoOTn0wJ
+-- Applicable starting with Orthanc 0.9.5
+
+function OnStoredInstance(instanceId, tags, metadata, origin)
+   -- Do not modify twice the same file
+   if origin['RequestOrigin'] ~= 'Lua' then
+      local replace = {}
+      replace['0010,1002'] = {}
+      replace['0010,1002'][1] = {}
+      replace['0010,1002'][1]['PatientID'] = 'Hello'
+      replace['0010,1002'][2] = {}
+      replace['0010,1002'][2]['PatientID'] = 'World'
+
+      local request = {}
+      request['Replace'] = replace
+
+      -- Create the modified instance
+      local modified = RestApiPost('/instances/' .. instanceId .. '/modify',
+                                   DumpJson(request))
+
+      -- Upload the modified instance to the Orthanc store
+      RestApiPost('/instances/', modified)
+
+      -- Delete the original instance
+      RestApiDelete('/instances/' .. instanceId)
+   end
+end
--- a/THANKS	Tue Oct 06 14:44:52 2015 +0200
+++ b/THANKS	Mon Oct 12 14:47:58 2015 +0200
@@ -28,7 +28,6 @@
 * Vincent Kersten <vincent1234567@gmail.com>, for DICOMDIR in the GUI.
 * Emsy Chan <emlscs@yahoo.com>, for various contributions
   and sample DICOM files.
-* Mikhail <mp39590@gmail.com>, for FreeBSD support.
 
 
 Thanks also to all the contributors active in our Google Group:
@@ -49,6 +48,12 @@
 * Mario Ceresa <mrceresa@gmail.com>, for help about packaging.
 
 
+FreeBSD
+-------
+
+* Mikhail <mp39590@gmail.com>, for FreeBSD packaging.
+
+
 Artwork
 -------
 
--- a/UnitTestsSources/FromDcmtkTests.cpp	Tue Oct 06 14:44:52 2015 +0200
+++ b/UnitTestsSources/FromDcmtkTests.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -36,6 +36,7 @@
 #include "../OrthancServer/FromDcmtkBridge.h"
 #include "../OrthancServer/OrthancInitialization.h"
 #include "../OrthancServer/DicomModification.h"
+#include "../OrthancServer/ServerToolbox.h"
 #include "../Core/OrthancException.h"
 #include "../Core/Images/ImageBuffer.h"
 #include "../Core/Images/PngReader.h"
@@ -43,6 +44,8 @@
 #include "../Core/Uuid.h"
 #include "../Resources/EncodingTests.h"
 
+#include <dcmtk/dcmdata/dcelem.h>
+
 using namespace Orthanc;
 
 TEST(DicomFormat, Tag)
@@ -102,7 +105,7 @@
   ParsedDicomFile o;
   o.Replace(DICOM_TAG_PATIENT_NAME, "coucou");
   ASSERT_FALSE(o.GetTagValue(s, privateTag));
-  o.Insert(privateTag, "private tag");
+  o.Insert(privateTag, "private tag", false);
   ASSERT_TRUE(o.GetTagValue(s, privateTag));
   ASSERT_STREQ("private tag", s.c_str());
 
@@ -204,7 +207,7 @@
     std::string source(testEncodingsEncoded[i]);
     std::string expected(testEncodingsExpected[i]);
     std::string s = Toolbox::ConvertToUtf8(source, testEncodings[i]);
-    std::cout << EnumerationToString(testEncodings[i]) << std::endl;
+    //std::cout << EnumerationToString(testEncodings[i]) << std::endl;
     EXPECT_EQ(expected, s);
   }
 }
@@ -259,13 +262,15 @@
 {
   for (unsigned int i = 0; i < testEncodingsCount; i++)
   {
-    std::cout << EnumerationToString(testEncodings[i]) << std::endl;
+    //std::cout << EnumerationToString(testEncodings[i]) << std::endl;
     std::string dicom;
 
     {
       ParsedDicomFile f;
       f.SetEncoding(testEncodings[i]);
-      f.Insert(DICOM_TAG_PATIENT_NAME, testEncodingsEncoded[i]);
+
+      std::string s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i]);
+      f.Insert(DICOM_TAG_PATIENT_NAME, s, false);
       f.SaveToMemoryBuffer(dicom);
     }
 
@@ -299,3 +304,219 @@
   ASSERT_EQ(ValueRepresentation_Other, 
             FromDcmtkBridge::GetValueRepresentation(DICOM_TAG_PATIENT_ID));
 }
+
+
+
+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)
+{
+  std::auto_ptr<DcmElement> element;
+
+  {
+    Json::Value a;
+    a = "Hello";
+    element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false, Encoding_Utf8));
+
+    Json::Value b;
+    FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, 0, Encoding_Ascii);
+    ASSERT_EQ("Hello", b["0010,0010"].asString());
+  }
+
+  {
+    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);
+  }
+
+  {
+    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);
+  }
+
+  {
+    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));
+
+    Json::Value b;
+    FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, 0, Encoding_Ascii);
+    ASSERT_EQ("Hello", b["0010,0010"].asString());
+  }
+
+  {
+    Json::Value a = Json::arrayValue;
+    CreateSampleJson(a);
+    element.reset(FromDcmtkBridge::FromJson(REFERENCED_STUDY_SEQUENCE, a, true, Encoding_Utf8));
+
+    {
+      Json::Value b;
+      FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, 0, Encoding_Ascii);
+      ASSERT_EQ(Json::arrayValue, b["0008,1110"].type());
+      ASSERT_EQ(2, b["0008,1110"].size());
+      
+      Json::Value::ArrayIndex i = (b["0008,1110"][0]["0010,0010"].asString() == "Hello") ? 0 : 1;
+
+      ASSERT_EQ(3, b["0008,1110"][i].size());
+      ASSERT_EQ(2, b["0008,1110"][1 - i].size());
+      ASSERT_EQ(b["0008,1110"][i]["0010,0010"].asString(), "Hello");
+      ASSERT_EQ(b["0008,1110"][i]["0010,0020"].asString(), "World");
+      ASSERT_EQ(b["0008,1110"][i]["0008,1030"].asString(), "Toto");
+      ASSERT_EQ(b["0008,1110"][1 - i]["0010,0010"].asString(), "Hello2");
+      ASSERT_EQ(b["0008,1110"][1 - i]["0010,0020"].asString(), "World2");
+    }
+
+    {
+      Json::Value b;
+      FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Full, 0, Encoding_Ascii);
+
+      Json::Value c;
+      Toolbox::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", false);
+  ASSERT_THROW(f.Insert(DICOM_TAG_PATIENT_ID, "Hello", false), 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;
+    Toolbox::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");
+}
+
+
+TEST(ParsedDicomFile, JsonEncoding)
+{
+  ParsedDicomFile f;
+
+  for (unsigned int i = 0; i < testEncodingsCount; i++)
+  {
+    if (testEncodings[i] != Encoding_Windows1251)
+    {
+      //std::cout << EnumerationToString(testEncodings[i]) << std::endl;
+      f.SetEncoding(testEncodings[i]);
+
+      if (testEncodings[i] != Encoding_Ascii)
+      {
+        ASSERT_EQ(testEncodings[i], f.GetEncoding());
+      }
+
+      Json::Value s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i]);
+      f.Replace(DICOM_TAG_PATIENT_NAME, s, false);
+
+      Json::Value v;
+      f.ToJson(v, DicomToJsonFormat_Simple, 0);
+      ASSERT_EQ(v["PatientName"].asString(), std::string(testEncodingsExpected[i]));
+    }
+  }
+}