changeset 1982:b5d4f9c156ad

Modification of instances can now replace PixelData
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 22 Apr 2016 10:28:55 +0200
parents 4b545a8b1f95
children 0af087300b26
files NEWS OrthancServer/DicomModification.cpp OrthancServer/OrthancFindRequestHandler.cpp OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp OrthancServer/ParsedDicomFile.cpp OrthancServer/ParsedDicomFile.h OrthancServer/ToDcmtkBridge.cpp OrthancServer/ToDcmtkBridge.h OrthancServer/main.cpp UnitTestsSources/FromDcmtkTests.cpp
diffstat 10 files changed, 184 insertions(+), 146 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Fri Apr 22 09:05:06 2016 +0200
+++ b/NEWS	Fri Apr 22 10:28:55 2016 +0200
@@ -39,6 +39,8 @@
 * Use of HTTP status 403 Forbidden (instead of 401) if access to a REST resource is disallowed
 * Option "HttpsVerifyPeers" can be used to connect against self-signed HTTPS certificates
 * Macro "__linux" (now obsolete) replaced by macro "__linux__" (maybe solves Debian bug #821011)
+* Modification of instances can now replace PixelData (resp. EncapsulatedDocument) with 
+  provided a PNG/JPEG image (resp. PDF file) if it is encoded using Data URI Scheme
 
 
 Version 1.0.0 (2015/12/15)
--- a/OrthancServer/DicomModification.cpp	Fri Apr 22 09:05:06 2016 +0200
+++ b/OrthancServer/DicomModification.cpp	Fri Apr 22 10:28:55 2016 +0200
@@ -141,7 +141,7 @@
       mapped = previous->second;
     }    
 
-    dicom.Replace(*tag, mapped);
+    dicom.Replace(*tag, mapped, false /* don't try and decode data URI scheme for UIDs */, DicomReplaceMode_InsertIfAbsent);
   }
   
   DicomModification::DicomModification()
@@ -459,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, true /* decode data URI scheme */, DicomReplaceMode_InsertIfAbsent);
     }
 
     // (4) Update the DICOM identifiers
--- a/OrthancServer/OrthancFindRequestHandler.cpp	Fri Apr 22 09:05:06 2016 +0200
+++ b/OrthancServer/OrthancFindRequestHandler.cpp	Fri Apr 22 10:28:55 2016 +0200
@@ -393,7 +393,7 @@
             content.append(item);
           }
 
-          dicom.Replace(*tag, content, false);
+          dicom.Replace(*tag, content, false, DicomReplaceMode_InsertIfAbsent);
         }
       }
 
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Fri Apr 22 09:05:06 2016 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Fri Apr 22 10:28:55 2016 +0200
@@ -480,7 +480,8 @@
       }
       else
       {
-        dicom.Replace(tag, value);
+        // This is V1, don't try and decode data URI scheme
+        dicom.ReplacePlainString(tag, value);
       }
     }
   }
@@ -526,7 +527,7 @@
         }
         else
         {
-          dicom.Replace(tag, tags[name], decodeBinaryTags);
+          dicom.Replace(tag, tags[name], decodeBinaryTags, DicomReplaceMode_InsertIfAbsent);
         }
       }
     }
@@ -542,8 +543,8 @@
     assert(content.size() > 0);
     ServerContext& context = OrthancRestApi::GetContext(call);
 
-    base.Replace(DICOM_TAG_IMAGES_IN_ACQUISITION, boost::lexical_cast<std::string>(content.size()));
-    base.Replace(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS, "1");
+    base.ReplacePlainString(DICOM_TAG_IMAGES_IN_ACQUISITION, boost::lexical_cast<std::string>(content.size()));
+    base.ReplacePlainString(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS, "1");
 
     std::string someInstance;
 
@@ -580,8 +581,8 @@
         }
 
         dicom->EmbedContent(payload->asString());
-        dicom->Replace(DICOM_TAG_INSTANCE_NUMBER, boost::lexical_cast<std::string>(i + 1));
-        dicom->Replace(DICOM_TAG_IMAGE_INDEX, boost::lexical_cast<std::string>(i + 1));
+        dicom->ReplacePlainString(DICOM_TAG_INSTANCE_NUMBER, boost::lexical_cast<std::string>(i + 1));
+        dicom->ReplacePlainString(DICOM_TAG_IMAGE_INDEX, boost::lexical_cast<std::string>(i + 1));
 
         StoreCreatedInstance(someInstance, call, *dicom);
       }
@@ -730,12 +731,12 @@
           const Json::Value& tag = siblingTags[t];
           if (tag["Type"] == "Null")
           {
-            dicom.Replace(*it, "");
+            dicom.ReplacePlainString(*it, "");
           }
           else if (tag["Type"] == "String")
           {
             std::string value = tag["Value"].asString();
-            dicom.Replace(*it, Toolbox::ConvertFromUtf8(value, dicom.GetEncoding()));
+            dicom.ReplacePlainString(*it, Toolbox::ConvertFromUtf8(value, dicom.GetEncoding()));
           }
         }
       }
@@ -758,26 +759,26 @@
     // Inject time-related information
     std::string date, time;
     Toolbox::GetNowDicom(date, time);
-    dicom.Replace(DICOM_TAG_ACQUISITION_DATE, date);
-    dicom.Replace(DICOM_TAG_ACQUISITION_TIME, time);
-    dicom.Replace(DICOM_TAG_CONTENT_DATE, date);
-    dicom.Replace(DICOM_TAG_CONTENT_TIME, time);
-    dicom.Replace(DICOM_TAG_INSTANCE_CREATION_DATE, date);
-    dicom.Replace(DICOM_TAG_INSTANCE_CREATION_TIME, time);
+    dicom.ReplacePlainString(DICOM_TAG_ACQUISITION_DATE, date);
+    dicom.ReplacePlainString(DICOM_TAG_ACQUISITION_TIME, time);
+    dicom.ReplacePlainString(DICOM_TAG_CONTENT_DATE, date);
+    dicom.ReplacePlainString(DICOM_TAG_CONTENT_TIME, time);
+    dicom.ReplacePlainString(DICOM_TAG_INSTANCE_CREATION_DATE, date);
+    dicom.ReplacePlainString(DICOM_TAG_INSTANCE_CREATION_TIME, time);
 
     if (parentType == ResourceType_Patient ||
         parentType == ResourceType_Study ||
         parentType == ResourceType_Instance /* no parent */)
     {
-      dicom.Replace(DICOM_TAG_SERIES_DATE, date);
-      dicom.Replace(DICOM_TAG_SERIES_TIME, time);
+      dicom.ReplacePlainString(DICOM_TAG_SERIES_DATE, date);
+      dicom.ReplacePlainString(DICOM_TAG_SERIES_TIME, time);
     }
 
     if (parentType == ResourceType_Patient ||
         parentType == ResourceType_Instance /* no parent */)
     {
-      dicom.Replace(DICOM_TAG_STUDY_DATE, date);
-      dicom.Replace(DICOM_TAG_STUDY_TIME, time);
+      dicom.ReplacePlainString(DICOM_TAG_STUDY_DATE, date);
+      dicom.ReplacePlainString(DICOM_TAG_STUDY_TIME, time);
     }
 
 
--- a/OrthancServer/ParsedDicomFile.cpp	Fri Apr 22 09:05:06 2016 +0200
+++ b/OrthancServer/ParsedDicomFile.cpp	Fri Apr 22 10:28:55 2016 +0200
@@ -581,16 +581,26 @@
       throw OrthancException(ErrorCode_AlreadyExistingTag);
     }
 
+    if (decodeDataUriScheme &&
+        value.type() == Json::stringValue &&
+        (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT ||
+         tag == DICOM_TAG_PIXEL_DATA))
+    {
+      if (EmbedContentInternal(value.asString()))
+      {
+        return;
+      }
+    }
+
     InvalidateCache();
-
     std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, GetEncoding()));
     InsertInternal(*pimpl_->file_->getDataset(), element.release());
   }
 
 
-  static bool IsReplaceAllowed(DcmDataset& dicom,
-                               const DcmTagKey& tag,
-                               DicomReplaceMode mode)
+  static bool CanReplaceProceed(DcmDataset& dicom,
+                                const DcmTagKey& tag,
+                                DicomReplaceMode mode)
   {
     if (dicom.findAndDeleteElement(tag).good())
     {
@@ -663,30 +673,42 @@
 
     if (tag == DICOM_TAG_SOP_CLASS_UID)
     {
-      Replace(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, *decoded, DicomReplaceMode_InsertIfAbsent);
+      ReplacePlainString(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, *decoded);
     }
 
     if (tag == DICOM_TAG_SOP_INSTANCE_UID)
     {
-      Replace(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, *decoded, DicomReplaceMode_InsertIfAbsent);
+      ReplacePlainString(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, *decoded);
     }    
   }
 
 
   void ParsedDicomFile::Replace(const DicomTag& tag,
                                 const std::string& utf8Value,
+                                bool decodeDataUriScheme,
                                 DicomReplaceMode mode)
   {
     InvalidateCache();
 
-    std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag));
-    FromDcmtkBridge::FillElementWithString(*element, tag, utf8Value, false, GetEncoding());
+    DcmDataset& dicom = *pimpl_->file_->getDataset();
+    if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode))
+    {
+      // Either the tag was previously existing (and now removed), or
+      // the replace mode was set to "InsertIfAbsent"
 
-    DcmDataset& dicom = *pimpl_->file_->getDataset();
-    if (IsReplaceAllowed(dicom, element->getTag(), mode))
-    {
-      // Either the tag was previously existing, or the replace mode
-      // was set to "InsertIfAbsent"
+      if (decodeDataUriScheme &&
+          (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT ||
+           tag == DICOM_TAG_PIXEL_DATA))
+      {
+        if (EmbedContentInternal(utf8Value))
+        {
+          return;
+        }
+      }
+
+      std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag));
+      FromDcmtkBridge::FillElementWithString(*element, tag, utf8Value, decodeDataUriScheme, GetEncoding());
+
       InsertInternal(dicom, element.release());
       UpdateStorageUid(tag, utf8Value, false);
     }
@@ -700,14 +722,24 @@
   {
     InvalidateCache();
 
-    std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, GetEncoding()));
+    DcmDataset& dicom = *pimpl_->file_->getDataset();
+    if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode))
+    {
+      // Either the tag was previously existing (and now removed), or
+      // the replace mode was set to "InsertIfAbsent"
 
-    DcmDataset& dicom = *pimpl_->file_->getDataset();
-    if (IsReplaceAllowed(dicom, element->getTag(), mode))
-    {
-      // Either the tag was previously existing, or the replace mode
-      // was set to "InsertIfAbsent"
-      InsertInternal(dicom, element.release());
+      if (decodeDataUriScheme &&
+          value.type() == Json::stringValue &&
+          (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT ||
+           tag == DICOM_TAG_PIXEL_DATA))
+      {
+        if (EmbedContentInternal(value.asString()))
+        {
+          return;
+        }
+      }
+
+      InsertInternal(dicom, FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, GetEncoding()));
 
       if (tag == DICOM_TAG_SOP_CLASS_UID ||
           tag == DICOM_TAG_SOP_INSTANCE_UID)
@@ -833,10 +865,10 @@
 
     if (createIdentifiers)
     {
-      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));
-      Replace(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
+      ReplacePlainString(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient));
+      ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study));
+      ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series));
+      ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
     }
   }
 
@@ -878,7 +910,7 @@
     pimpl_->file_.reset(dynamic_cast<DcmFileFormat*>(other.pimpl_->file_->clone()));
 
     // Create a new instance-level identifier
-    Replace(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
+    ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
   }
 
 
@@ -912,12 +944,12 @@
   }
 
 
-  void ParsedDicomFile::EmbedContent(const std::string& dataUriScheme)
+  bool ParsedDicomFile::EmbedContentInternal(const std::string& dataUriScheme)
   {
     std::string mime, content;
     if (!Toolbox::DecodeDataUriScheme(mime, content, dataUriScheme))
     {
-      throw OrthancException(ErrorCode_BadFileFormat);
+      return false;
     }
 
     Toolbox::ToLowerCase(mime);
@@ -936,14 +968,23 @@
       LOG(ERROR) << "Unsupported MIME type for the content of a new DICOM file: " << mime;
       throw OrthancException(ErrorCode_NotImplemented);
     }
+
+    return true;
+  }
+
+
+  void ParsedDicomFile::EmbedContent(const std::string& dataUriScheme)
+  {
+    if (!EmbedContentInternal(dataUriScheme))
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
   }
 
 
   void ParsedDicomFile::EmbedImage(const std::string& mime,
                                    const std::string& content)
   {
-    InvalidateCache();
-
     if (mime == "image/png")
     {
       PngReader reader;
@@ -974,6 +1015,8 @@
       throw OrthancException(ErrorCode_NotImplemented);
     }
 
+    InvalidateCache();
+
     if (accessor.GetFormat() == PixelFormat_RGBA32)
     {
       LOG(WARNING) << "Getting rid of the alpha channel when embedding a RGBA image inside DICOM";
@@ -982,49 +1025,49 @@
     // http://dicomiseasy.blogspot.be/2012/08/chapter-12-pixel-data.html
 
     Remove(DICOM_TAG_PIXEL_DATA);
-    Replace(DICOM_TAG_COLUMNS, boost::lexical_cast<std::string>(accessor.GetWidth()));
-    Replace(DICOM_TAG_ROWS, boost::lexical_cast<std::string>(accessor.GetHeight()));
-    Replace(DICOM_TAG_SAMPLES_PER_PIXEL, "1");
-    Replace(DICOM_TAG_NUMBER_OF_FRAMES, "1");
+    ReplacePlainString(DICOM_TAG_COLUMNS, boost::lexical_cast<std::string>(accessor.GetWidth()));
+    ReplacePlainString(DICOM_TAG_ROWS, boost::lexical_cast<std::string>(accessor.GetHeight()));
+    ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "1");
+    ReplacePlainString(DICOM_TAG_NUMBER_OF_FRAMES, "1");
 
     if (accessor.GetFormat() == PixelFormat_SignedGrayscale16)
     {
-      Replace(DICOM_TAG_PIXEL_REPRESENTATION, "1");
+      ReplacePlainString(DICOM_TAG_PIXEL_REPRESENTATION, "1");
     }
     else
     {
-      Replace(DICOM_TAG_PIXEL_REPRESENTATION, "0");  // Unsigned pixels
+      ReplacePlainString(DICOM_TAG_PIXEL_REPRESENTATION, "0");  // Unsigned pixels
     }
 
-    Replace(DICOM_TAG_PLANAR_CONFIGURATION, "0");  // Color channels are interleaved
-    Replace(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "MONOCHROME2");
+    ReplacePlainString(DICOM_TAG_PLANAR_CONFIGURATION, "0");  // Color channels are interleaved
+    ReplacePlainString(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "MONOCHROME2");
 
     unsigned int bytesPerPixel = 0;
 
     switch (accessor.GetFormat())
     {
       case PixelFormat_Grayscale8:
-        Replace(DICOM_TAG_BITS_ALLOCATED, "8");
-        Replace(DICOM_TAG_BITS_STORED, "8");
-        Replace(DICOM_TAG_HIGH_BIT, "7");
+        ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "8");
+        ReplacePlainString(DICOM_TAG_BITS_STORED, "8");
+        ReplacePlainString(DICOM_TAG_HIGH_BIT, "7");
         bytesPerPixel = 1;
         break;
 
       case PixelFormat_RGB24:
       case PixelFormat_RGBA32:
-        Replace(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "RGB");
-        Replace(DICOM_TAG_SAMPLES_PER_PIXEL, "3");
-        Replace(DICOM_TAG_BITS_ALLOCATED, "8");
-        Replace(DICOM_TAG_BITS_STORED, "8");
-        Replace(DICOM_TAG_HIGH_BIT, "7");
+        ReplacePlainString(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "RGB");
+        ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "3");
+        ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "8");
+        ReplacePlainString(DICOM_TAG_BITS_STORED, "8");
+        ReplacePlainString(DICOM_TAG_HIGH_BIT, "7");
         bytesPerPixel = 3;
         break;
 
       case PixelFormat_Grayscale16:
       case PixelFormat_SignedGrayscale16:
-        Replace(DICOM_TAG_BITS_ALLOCATED, "16");
-        Replace(DICOM_TAG_BITS_STORED, "16");
-        Replace(DICOM_TAG_HIGH_BIT, "15");
+        ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "16");
+        ReplacePlainString(DICOM_TAG_BITS_STORED, "16");
+        ReplacePlainString(DICOM_TAG_HIGH_BIT, "15");
         bytesPerPixel = 2;
         break;
 
@@ -1100,7 +1143,7 @@
     }
 
     std::string s = GetDicomSpecificCharacterSet(encoding);
-    Replace(DICOM_TAG_SPECIFIC_CHARACTER_SET, s, DicomReplaceMode_InsertIfAbsent);
+    ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, s);
   }
 
   void ParsedDicomFile::ToJson(Json::Value& target, 
@@ -1137,11 +1180,13 @@
       throw OrthancException(ErrorCode_BadFileFormat);
     }
 
-    Replace(DICOM_TAG_SOP_CLASS_UID, UID_EncapsulatedPDFStorage);
-    Replace(FromDcmtkBridge::Convert(DCM_Modality), "OT");
-    Replace(FromDcmtkBridge::Convert(DCM_ConversionType), "WSD");
-    Replace(FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument), "application/pdf");
-    //Replace(FromDcmtkBridge::Convert(DCM_SeriesNumber), "1");
+    InvalidateCache();
+
+    ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, UID_EncapsulatedPDFStorage);
+    ReplacePlainString(FromDcmtkBridge::Convert(DCM_Modality), "OT");
+    ReplacePlainString(FromDcmtkBridge::Convert(DCM_ConversionType), "WSD");
+    ReplacePlainString(FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument), "application/pdf");
+    //ReplacePlainString(FromDcmtkBridge::Convert(DCM_SeriesNumber), "1");
 
     std::auto_ptr<DcmPolymorphOBOW> element(new DcmPolymorphOBOW(DCM_EncapsulatedDocument));
 
@@ -1249,7 +1294,7 @@
       }
       else if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET)
       {
-        result->Replace(tag, value, decodeDataUriScheme);
+        result->Replace(tag, value, decodeDataUriScheme, DicomReplaceMode_InsertIfAbsent);
       }
     }
 
--- a/OrthancServer/ParsedDicomFile.h	Fri Apr 22 09:05:06 2016 +0200
+++ b/OrthancServer/ParsedDicomFile.h	Fri Apr 22 10:28:55 2016 +0200
@@ -59,6 +59,8 @@
 
     void InvalidateCache();
 
+    bool EmbedContentInternal(const std::string& dataUriScheme);
+
   public:
     ParsedDicomFile(bool createIdentifiers);  // Create a minimal DICOM instance
 
@@ -88,17 +90,24 @@
 
     void Replace(const DicomTag& tag,
                  const std::string& utf8Value,
-                 DicomReplaceMode mode = DicomReplaceMode_InsertIfAbsent);
+                 bool decodeDataUriScheme,
+                 DicomReplaceMode mode);
 
     void Replace(const DicomTag& tag,
                  const Json::Value& value,  // Assumed to be encoded with UTF-8
                  bool decodeDataUriScheme,
-                 DicomReplaceMode mode = DicomReplaceMode_InsertIfAbsent);
+                 DicomReplaceMode mode);
 
     void Insert(const DicomTag& tag,
                 const Json::Value& value,   // Assumed to be encoded with UTF-8
                 bool decodeDataUriScheme);
 
+    void ReplacePlainString(const DicomTag& tag,
+                            const std::string& utf8Value)
+    {
+      Replace(tag, utf8Value, false, DicomReplaceMode_InsertIfAbsent);
+    }
+
     void RemovePrivateTags()
     {
       RemovePrivateTagsInternal(NULL);
--- a/OrthancServer/ToDcmtkBridge.cpp	Fri Apr 22 09:05:06 2016 +0200
+++ b/OrthancServer/ToDcmtkBridge.cpp	Fri Apr 22 10:28:55 2016 +0200
@@ -39,12 +39,6 @@
 
 namespace Orthanc
 {
-  DcmTagKey ToDcmtkBridge::Convert(const DicomTag& tag)
-  {
-    return DcmTagKey(tag.GetGroup(), tag.GetElement());
-  }
-
-
   DcmDataset* ToDcmtkBridge::Convert(const DicomMap& map)
   {
     std::auto_ptr<DcmDataset> result(new DcmDataset);
--- a/OrthancServer/ToDcmtkBridge.h	Fri Apr 22 09:05:06 2016 +0200
+++ b/OrthancServer/ToDcmtkBridge.h	Fri Apr 22 10:28:55 2016 +0200
@@ -40,7 +40,10 @@
   class ToDcmtkBridge
   {
   public:
-    static DcmTagKey Convert(const DicomTag& tag);
+    static DcmTagKey Convert(const DicomTag& tag)
+    {
+      return DcmTagKey(tag.GetGroup(), tag.GetElement());
+    }
 
     static DcmDataset* Convert(const DicomMap& map);
   };
--- a/OrthancServer/main.cpp	Fri Apr 22 09:05:06 2016 +0200
+++ b/OrthancServer/main.cpp	Fri Apr 22 10:28:55 2016 +0200
@@ -1163,23 +1163,6 @@
     {
       OrthancInitialize(configurationFile);
 
-      if (0)
-      {
-        // TODO REMOVE THIS TEST
-        DicomUserConnection c;
-        c.SetRemoteHost("localhost");
-        c.SetRemotePort(4243);
-        c.SetRemoteApplicationEntityTitle("ORTHANCTEST");
-        c.Open();
-        ParsedDicomFile f(false);
-        f.Replace(DICOM_TAG_PATIENT_NAME, "M*");
-        DicomFindAnswers a;
-        c.FindWorklist(a, f);
-        Json::Value j;
-        a.ToJson(j, true);
-        std::cout << j;
-      }
-
       bool restart = StartOrthanc(argc, argv, allowDatabaseUpgrade);
       if (restart)
       {
--- a/UnitTestsSources/FromDcmtkTests.cpp	Fri Apr 22 09:05:06 2016 +0200
+++ b/UnitTestsSources/FromDcmtkTests.cpp	Fri Apr 22 10:28:55 2016 +0200
@@ -76,8 +76,8 @@
   DicomModification m;
   m.SetupAnonymization();
   //m.SetLevel(DicomRootLevel_Study);
-  //m.Replace(DICOM_TAG_PATIENT_ID, "coucou");
-  //m.Replace(DICOM_TAG_PATIENT_NAME, "coucou");
+  //m.ReplacePlainString(DICOM_TAG_PATIENT_ID, "coucou");
+  //m.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "coucou");
 
   ParsedDicomFile o(true);
   o.SaveToFile("UnitTestsResults/anon.dcm");
@@ -88,7 +88,7 @@
     sprintf(b, "UnitTestsResults/anon%06d.dcm", i);
     std::auto_ptr<ParsedDicomFile> f(o.Clone());
     if (i > 4)
-      o.Replace(DICOM_TAG_SERIES_INSTANCE_UID, "coucou");
+      o.ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, "coucou");
     m.Apply(*f);
     f->SaveToFile(b);
   }
@@ -108,21 +108,21 @@
 
   std::string s;
   ParsedDicomFile o(true);
-  o.Replace(DICOM_TAG_PATIENT_NAME, "coucou");
+  o.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "coucou");
   ASSERT_FALSE(o.GetTagValue(s, privateTag));
   o.Insert(privateTag, "private tag", false);
   ASSERT_TRUE(o.GetTagValue(s, privateTag));
   ASSERT_STREQ("private tag", s.c_str());
 
   ASSERT_FALSE(o.GetTagValue(s, privateTag2));
-  ASSERT_THROW(o.Replace(privateTag2, "hello", DicomReplaceMode_ThrowIfAbsent), OrthancException);
+  ASSERT_THROW(o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_ThrowIfAbsent), OrthancException);
   ASSERT_FALSE(o.GetTagValue(s, privateTag2));
-  o.Replace(privateTag2, "hello", DicomReplaceMode_IgnoreIfAbsent);
+  o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_IgnoreIfAbsent);
   ASSERT_FALSE(o.GetTagValue(s, privateTag2));
-  o.Replace(privateTag2, "hello", DicomReplaceMode_InsertIfAbsent);
+  o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_InsertIfAbsent);
   ASSERT_TRUE(o.GetTagValue(s, privateTag2));
   ASSERT_STREQ("hello", s.c_str());
-  o.Replace(privateTag2, "hello world");
+  o.ReplacePlainString(privateTag2, "hello world");
   ASSERT_TRUE(o.GetTagValue(s, privateTag2));
   ASSERT_STREQ("hello world", s.c_str());
 
@@ -177,7 +177,7 @@
   // Check box in Graylevel8
   s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDDcB53FulQAAAElJREFUGNNtj0sSAEEEQ1+U+185s1CtmRkblQ9CZldsKHJDk6DLGLJa6chjh0ooQmpjXMM86zPwydGEj6Ed/UGykkEM8X+p3u8/8LcOJIWLGeMAAAAASUVORK5CYII=";
   o.EmbedContent(s);
-  //o.Replace(DICOM_TAG_SOP_CLASS_UID, UID_DigitalXRayImageStorageForProcessing);
+  //o.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, UID_DigitalXRayImageStorageForProcessing);
   o.SaveToFile("UnitTestsResults/png3.dcm");
 
 
@@ -416,21 +416,22 @@
 
   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");  // (**)
+  f.ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, "Toto");  // (*)
+  f.ReplacePlainString(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_THROW(f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"),
+                         false, DicomReplaceMode_ThrowIfAbsent), OrthancException);
+  f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"), false, DicomReplaceMode_IgnoreIfAbsent);
   ASSERT_FALSE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
-  f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession", DicomReplaceMode_InsertIfAbsent);
+  f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"), false, DicomReplaceMode_InsertIfAbsent);
   ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
   ASSERT_EQ(s, "Accession");
-  f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession2", DicomReplaceMode_IgnoreIfAbsent);
+  f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession2"), false, DicomReplaceMode_IgnoreIfAbsent);
   ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
   ASSERT_EQ(s, "Accession2");
-  f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession3", DicomReplaceMode_ThrowIfAbsent);
+  f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession3"), false, DicomReplaceMode_ThrowIfAbsent);
   ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
   ASSERT_EQ(s, "Accession3");
 
@@ -486,8 +487,8 @@
   }
 
   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);  // (**)
+  f.Replace(DICOM_TAG_SOP_INSTANCE_UID, a, false, DicomReplaceMode_InsertIfAbsent);  // (*)
+  f.Replace(DICOM_TAG_SOP_CLASS_UID, a, true, DicomReplaceMode_InsertIfAbsent);  // (**)
 
   std::string s;
   ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_INSTANCE_UID));
@@ -518,7 +519,7 @@
       }
 
       Json::Value s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i]);
-      f.Replace(DICOM_TAG_PATIENT_NAME, s, false);
+      f.Replace(DICOM_TAG_PATIENT_NAME, s, false, DicomReplaceMode_InsertIfAbsent);
 
       Json::Value v;
       f.ToJson(v, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0);
@@ -651,7 +652,7 @@
 
   {
     ParsedDicomFile d(true);
-    d.Replace(DICOM_TAG_PATIENT_ID, "my");
+    d.ReplacePlainString(DICOM_TAG_PATIENT_ID, "my");
     a.Add(d);
   }
 
@@ -790,12 +791,12 @@
 
   {
     ParsedDicomFile f(true);
-    f.Replace(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.7");
-    f.Replace(DICOM_TAG_STUDY_INSTANCE_UID, "1.2.276.0.7230010.3.1.2.2831176407.321.1458901422.884998");
-    f.Replace(DICOM_TAG_PATIENT_ID, "ORTHANC");
-    f.Replace(DICOM_TAG_PATIENT_NAME, "Orthanc");
-    f.Replace(DICOM_TAG_STUDY_DESCRIPTION, "Patterns");
-    f.Replace(DICOM_TAG_SERIES_DESCRIPTION, "Grayscale8");
+    f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.7");
+    f.ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, "1.2.276.0.7230010.3.1.2.2831176407.321.1458901422.884998");
+    f.ReplacePlainString(DICOM_TAG_PATIENT_ID, "ORTHANC");
+    f.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "Orthanc");
+    f.ReplacePlainString(DICOM_TAG_STUDY_DESCRIPTION, "Patterns");
+    f.ReplacePlainString(DICOM_TAG_SERIES_DESCRIPTION, "Grayscale8");
     f.EmbedImage(image);
 
     f.SaveToFile(PATH);
@@ -852,12 +853,12 @@
 
   {
     ParsedDicomFile f(true);
-    f.Replace(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.7");
-    f.Replace(DICOM_TAG_STUDY_INSTANCE_UID, "1.2.276.0.7230010.3.1.2.2831176407.321.1458901422.884998");
-    f.Replace(DICOM_TAG_PATIENT_ID, "ORTHANC");
-    f.Replace(DICOM_TAG_PATIENT_NAME, "Orthanc");
-    f.Replace(DICOM_TAG_STUDY_DESCRIPTION, "Patterns");
-    f.Replace(DICOM_TAG_SERIES_DESCRIPTION, "RGB24");
+    f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.7");
+    f.ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, "1.2.276.0.7230010.3.1.2.2831176407.321.1458901422.884998");
+    f.ReplacePlainString(DICOM_TAG_PATIENT_ID, "ORTHANC");
+    f.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "Orthanc");
+    f.ReplacePlainString(DICOM_TAG_STUDY_DESCRIPTION, "Patterns");
+    f.ReplacePlainString(DICOM_TAG_SERIES_DESCRIPTION, "RGB24");
     f.EmbedImage(image);
 
     f.SaveToFile(PATH);
@@ -906,12 +907,12 @@
 
   {
     ParsedDicomFile f(true);
-    f.Replace(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.7");
-    f.Replace(DICOM_TAG_STUDY_INSTANCE_UID, "1.2.276.0.7230010.3.1.2.2831176407.321.1458901422.884998");
-    f.Replace(DICOM_TAG_PATIENT_ID, "ORTHANC");
-    f.Replace(DICOM_TAG_PATIENT_NAME, "Orthanc");
-    f.Replace(DICOM_TAG_STUDY_DESCRIPTION, "Patterns");
-    f.Replace(DICOM_TAG_SERIES_DESCRIPTION, "Grayscale16");
+    f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.7");
+    f.ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, "1.2.276.0.7230010.3.1.2.2831176407.321.1458901422.884998");
+    f.ReplacePlainString(DICOM_TAG_PATIENT_ID, "ORTHANC");
+    f.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "Orthanc");
+    f.ReplacePlainString(DICOM_TAG_STUDY_DESCRIPTION, "Patterns");
+    f.ReplacePlainString(DICOM_TAG_SERIES_DESCRIPTION, "Grayscale16");
     f.EmbedImage(image);
 
     f.SaveToFile(PATH);
@@ -960,12 +961,12 @@
 
   {
     ParsedDicomFile f(true);
-    f.Replace(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.7");
-    f.Replace(DICOM_TAG_STUDY_INSTANCE_UID, "1.2.276.0.7230010.3.1.2.2831176407.321.1458901422.884998");
-    f.Replace(DICOM_TAG_PATIENT_ID, "ORTHANC");
-    f.Replace(DICOM_TAG_PATIENT_NAME, "Orthanc");
-    f.Replace(DICOM_TAG_STUDY_DESCRIPTION, "Patterns");
-    f.Replace(DICOM_TAG_SERIES_DESCRIPTION, "SignedGrayscale16");
+    f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.7");
+    f.ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, "1.2.276.0.7230010.3.1.2.2831176407.321.1458901422.884998");
+    f.ReplacePlainString(DICOM_TAG_PATIENT_ID, "ORTHANC");
+    f.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "Orthanc");
+    f.ReplacePlainString(DICOM_TAG_STUDY_DESCRIPTION, "Patterns");
+    f.ReplacePlainString(DICOM_TAG_SERIES_DESCRIPTION, "SignedGrayscale16");
     f.EmbedImage(image);
 
     f.SaveToFile(PATH);