view OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp @ 5911:bfae0fc2ea1b get-scu-test

Started to work on handling errors as warnings when trying to store instances whose SOPClassUID has not been accepted during the negotiation. Work to be finalized later
author Alain Mazy <am@orthanc.team>
date Mon, 09 Dec 2024 10:07:19 +0100
parents dedbf019a707
children
line wrap: on
line source

/**
 * Orthanc - A Lightweight, RESTful DICOM Store
 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 * Department, University Hospital of Liege, Belgium
 * Copyright (C) 2017-2023 Osimis S.A., Belgium
 * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
 * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
 *
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program. If not, see
 * <http://www.gnu.org/licenses/>.
 **/


#include "../PrecompiledHeaders.h"

#ifndef NOMINMAX
#define NOMINMAX
#endif

#if !defined(ORTHANC_SANDBOXED)
#  error The macro ORTHANC_SANDBOXED must be defined
#endif

#if !defined(DCMTK_VERSION_NUMBER)
#  error The macro DCMTK_VERSION_NUMBER must be defined
#endif

#include "FromDcmtkBridge.h"
#include "ToDcmtkBridge.h"
#include "../Compatibility.h"
#include "../Logging.h"
#include "../Toolbox.h"
#include "../OrthancException.h"

#if ORTHANC_SANDBOXED == 0
#  include "../TemporaryFile.h"
#endif

#include <list>
#include <limits>

#include <boost/lexical_cast.hpp>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/join.hpp>

#include <dcmtk/dcmdata/dcdeftag.h>
#include <dcmtk/dcmdata/dcdicent.h>
#include <dcmtk/dcmdata/dcdict.h>
#include <dcmtk/dcmdata/dcfilefo.h>
#include <dcmtk/dcmdata/dcistrmb.h>
#include <dcmtk/dcmdata/dcostrmb.h>
#include <dcmtk/dcmdata/dcpixel.h>
#include <dcmtk/dcmdata/dcuid.h>
#include <dcmtk/dcmdata/dcxfer.h>

#include <dcmtk/dcmdata/dcvrae.h>
#include <dcmtk/dcmdata/dcvras.h>
#include <dcmtk/dcmdata/dcvrat.h>
#include <dcmtk/dcmdata/dcvrcs.h>
#include <dcmtk/dcmdata/dcvrda.h>
#include <dcmtk/dcmdata/dcvrds.h>
#include <dcmtk/dcmdata/dcvrdt.h>
#include <dcmtk/dcmdata/dcvrfd.h>
#include <dcmtk/dcmdata/dcvrfl.h>
#include <dcmtk/dcmdata/dcvris.h>
#include <dcmtk/dcmdata/dcvrlo.h>
#include <dcmtk/dcmdata/dcvrlt.h>
#include <dcmtk/dcmdata/dcvrpn.h>
#include <dcmtk/dcmdata/dcvrsh.h>
#include <dcmtk/dcmdata/dcvrsl.h>
#include <dcmtk/dcmdata/dcvrss.h>
#include <dcmtk/dcmdata/dcvrst.h>
#include <dcmtk/dcmdata/dcvrtm.h>
#include <dcmtk/dcmdata/dcvrui.h>
#include <dcmtk/dcmdata/dcvrul.h>
#include <dcmtk/dcmdata/dcvrus.h>
#include <dcmtk/dcmdata/dcvrut.h>

#if DCMTK_VERSION_NUMBER >= 361
#  include <dcmtk/dcmdata/dcvruc.h>
#  include <dcmtk/dcmdata/dcvrur.h>
#endif

#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1
#  if !defined(ORTHANC_FRAMEWORK_INCLUDE_RESOURCES) || (ORTHANC_FRAMEWORK_INCLUDE_RESOURCES == 1)
#    include <OrthancFrameworkResources.h>
#  endif
#endif

#if ORTHANC_ENABLE_DCMTK_JPEG == 1
#  include <dcmtk/dcmjpeg/djdecode.h>
#  if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
#    include <dcmtk/dcmjpeg/djencode.h>
#  endif
#endif

#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
#  include <dcmtk/dcmjpls/djdecode.h>
#  if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
#    include <dcmtk/dcmjpls/djencode.h>
#  endif
#endif


#include <dcmtk/dcmdata/dcrledrg.h>
#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
#  include <dcmtk/dcmdata/dcrleerg.h>
#  include <dcmtk/dcmimage/diregist.h>  // include to support color images
#endif


static bool hasExternalDictionaries_ = false;


namespace Orthanc
{
  static bool IsBinaryTag(const DcmTag& key)
  {
    return (key.isUnknownVR() ||
            key.getEVR() == EVR_OB ||
            key.getEVR() == EVR_OW ||
            key.getEVR() == EVR_UN ||
            key.getEVR() == EVR_ox);
  }


#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1
  static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary,
                                     FrameworkResources::FileResourceId resource)
  {
    std::string content;
    FrameworkResources::GetFileResource(content, resource);

#if ORTHANC_SANDBOXED == 0
    TemporaryFile tmp;
    tmp.Write(content);

    if (!dictionary.loadDictionary(tmp.GetPath().c_str()))
    {
      throw OrthancException(ErrorCode_InternalError,
                             "Cannot read embedded dictionary. Under Windows, make sure that " 
                             "your TEMP directory does not contain special characters.");
    }
#else
    if (!dictionary.loadFromMemory(content))
    {
      throw OrthancException(ErrorCode_InternalError,
                             "Cannot read embedded dictionary. Under Windows, make sure that " 
                             "your TEMP directory does not contain special characters.");
    }
#endif
  }
#endif


  namespace
  {
    class DictionaryLocker : public boost::noncopyable
    {
    private:
      DcmDataDictionary& dictionary_;

    public:
      DictionaryLocker() : dictionary_(dcmDataDict.wrlock())
      {
      }

      ~DictionaryLocker()
      {
#if DCMTK_VERSION_NUMBER >= 364
        dcmDataDict.wrunlock();
#else
        dcmDataDict.unlock();
#endif
      }

      DcmDataDictionary& operator*()
      {
        return dictionary_;
      }

      DcmDataDictionary* operator->()
      {
        return &dictionary_;
      }
    };

    
    ORTHANC_FORCE_INLINE
    static std::string FloatToString(float v)
    {
      /**
       * From "boost::lexical_cast" documentation: "For more involved
       * conversions, such as where precision or formatting need tighter
       * control than is offered by the default behavior of
       * lexical_cast, the conventional stringstream approach is
       * recommended."
       * https://www.boost.org/doc/libs/1_65_0/doc/html/boost_lexical_cast.html
       * http://www.gotw.ca/publications/mill19.htm
       *
       * The precision of 17 corresponds to "defaultRealPrecision" in JsonCpp:
       * https://github.com/open-source-parsers/jsoncpp/blob/master/include/json/value.h
       **/

      //return boost::lexical_cast<std::string>(v);  // This was used in Orthanc <= 1.9.0
      
      std::ostringstream ss;
      ss << std::setprecision(17) << v;
      return ss.str();
    }


    ORTHANC_FORCE_INLINE
    static std::string DoubleToString(double v)
    {
      //return boost::lexical_cast<std::string>(v);  // This was used in Orthanc <= 1.9.0
      
      std::ostringstream ss;
      ss << std::setprecision(17) << v;
      return ss.str();
    }

    
#define DCMTK_TO_CTYPE_CONVERTER(converter, cType, dcmtkType, getter, toStringFunction) \
                                                                        \
    struct converter                                                    \
    {                                                                   \
      typedef cType CType;                                              \
                                                                        \
      ORTHANC_FORCE_INLINE                                              \
        static bool Apply(CType& result,                                \
                          DcmElement& element,                          \
                          size_t i)                                     \
      {                                                                 \
        return dynamic_cast<dcmtkType&>(element).getter(result, i).good(); \
      }                                                                 \
                                                                        \
      ORTHANC_FORCE_INLINE                                              \
        static std::string ToString(CType value)                        \
      {                                                                 \
        return toStringFunction(value);                                 \
      }                                                                 \
    };

    DCMTK_TO_CTYPE_CONVERTER(DcmtkToSint32Converter, Sint32, DcmSignedLong, getSint32, boost::lexical_cast<std::string>)
    DCMTK_TO_CTYPE_CONVERTER(DcmtkToSint16Converter, Sint16, DcmSignedShort, getSint16, boost::lexical_cast<std::string>)
    DCMTK_TO_CTYPE_CONVERTER(DcmtkToUint32Converter, Uint32, DcmUnsignedLong, getUint32, boost::lexical_cast<std::string>)
    DCMTK_TO_CTYPE_CONVERTER(DcmtkToUint16Converter, Uint16, DcmUnsignedShort, getUint16, boost::lexical_cast<std::string>)
    DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat32Converter, Float32, DcmFloatingPointSingle, getFloat32, FloatToString)
    DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDouble, getFloat64, DoubleToString)


    template <typename F>
    static DicomValue* ApplyDcmtkToCTypeConverter(DcmElement& element)
    {
      F f;
      typename F::CType value;

      if (element.getLength() > sizeof(typename F::CType)
          && (element.getLength() % sizeof(typename F::CType)) == 0)
      {
        size_t count = element.getLength() / sizeof(typename F::CType);
        std::vector<std::string> strings;
        for (size_t i = 0; i < count; i++) {
          if (f.Apply(value, element, i)) {
            strings.push_back(F::ToString(value));
          }
        }
        return new DicomValue(boost::algorithm::join(strings, "\\"), false);
      }
      else if (f.Apply(value, element, 0)) {
        return new DicomValue(F::ToString(value), false);
      }
      else {
        return new DicomValue;
      }
    }
  }


  void FromDcmtkBridge::InitializeDictionary(bool loadPrivateDictionary)
  {
    CLOG(INFO, DICOM) << "Using DCMTK version: " << DCMTK_VERSION_NUMBER;
    
#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1
    {
      DictionaryLocker locker;

      locker->clear();

      CLOG(INFO, DICOM) << "Loading the embedded dictionaries";
      /**
       * Do not load DICONDE dictionary, it breaks the other tags. The
       * command "strace storescu 2>&1 |grep dic" shows that DICONDE
       * dictionary is not loaded by storescu.
       **/
      //LoadEmbeddedDictionary(*locker, FrameworkResources::DICTIONARY_DICONDE);

      LoadEmbeddedDictionary(*locker, FrameworkResources::DICTIONARY_DICOM);

      if (loadPrivateDictionary)
      {
        CLOG(INFO, DICOM) << "Loading the embedded dictionary of private tags";
        LoadEmbeddedDictionary(*locker, FrameworkResources::DICTIONARY_PRIVATE);
      }
      else
      {
        CLOG(INFO, DICOM) << "The dictionary of private tags has not been loaded";
      }
    }
#else
    {
      std::vector<std::string> dictionaries;
      
      const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE);
      if (env != NULL)
      {
        // This mimics the behavior of DCMTK:
        // https://support.dcmtk.org/docs/file_envvars.html
#if defined(_WIN32)
        Toolbox::TokenizeString(dictionaries, std::string(env), ';');
#else
        Toolbox::TokenizeString(dictionaries, std::string(env), ':');
#endif
      }
      else
      {
        boost::filesystem::path base = DCMTK_DICTIONARY_DIR;
        dictionaries.push_back((base / "dicom.dic").string());

        if (loadPrivateDictionary)
        {
          dictionaries.push_back((base / "private.dic").string());
        }
      }

      LoadExternalDictionaries(dictionaries);
      hasExternalDictionaries_ = false;  // Fix the side-effect of "LoadExternalDictionaries()"
    }
#endif

    /* make sure data dictionary is loaded */
    if (!dcmDataDict.isDictionaryLoaded())
    {
      throw OrthancException(ErrorCode_InternalError,
                             "No DICOM dictionary loaded, check environment variable: " +
                             std::string(DCM_DICT_ENVIRONMENT_VARIABLE));
    }

    {
      // Test the dictionary with a simple DICOM tag
      DcmTag key(0x0010, 0x1030); // This is PatientWeight
      if (key.getEVR() != EVR_DS)
      {
        throw OrthancException(ErrorCode_InternalError,
                               "The DICOM dictionary has not been correctly read");
      }
    }
  }


  void FromDcmtkBridge::LoadExternalDictionaries(const std::vector<std::string>& dictionaries)
  {
    DictionaryLocker locker;

    CLOG(INFO, DICOM) << "Clearing the DICOM dictionary";
    locker->clear();

    for (size_t i = 0; i < dictionaries.size(); i++)
    {
      LOG(WARNING) << "Loading external DICOM dictionary: \"" << dictionaries[i] << "\"";
        
      if (!locker->loadDictionary(dictionaries[i].c_str()))
      {
        throw OrthancException(ErrorCode_InexistentFile);
      }
    }    

    hasExternalDictionaries_ = true;
  }


  void FromDcmtkBridge::RegisterDictionaryTag(const DicomTag& tag,
                                              ValueRepresentation vr,
                                              const std::string& name,
                                              unsigned int minMultiplicity,
                                              unsigned int maxMultiplicity,
                                              const std::string& privateCreator)
  {
    if (minMultiplicity < 1)
    {
      throw OrthancException(ErrorCode_ParameterOutOfRange);
    }

    bool arbitrary = false;
    if (maxMultiplicity == 0)
    {
      maxMultiplicity = DcmVariableVM;
      arbitrary = true;
    }
    else if (maxMultiplicity < minMultiplicity)
    {
      throw OrthancException(ErrorCode_ParameterOutOfRange);
    }
    
    DcmEVR evr = ToDcmtkBridge::Convert(vr);

    CLOG(INFO, DICOM) << "Registering tag in dictionary: (" << tag.Format() << ") "
                      << (DcmVR(evr).getValidVRName()) << " " 
                      << name << " (multiplicity: " << minMultiplicity << "-" 
                      << (arbitrary ? "n" : boost::lexical_cast<std::string>(maxMultiplicity)) << ")";

    std::unique_ptr<DcmDictEntry>  entry;
    if (privateCreator.empty())
    {
      if (tag.GetGroup() % 2 == 1)
      {
        char buf[128];
        sprintf(buf, "Warning: You are registering a private tag (%04x,%04x), "
                "but no private creator was associated with it", 
                tag.GetGroup(), tag.GetElement());
        LOG(WARNING) << buf;
      }

      entry.reset(new DcmDictEntry(tag.GetGroup(),
                                   tag.GetElement(),
                                   evr, name.c_str(),
                                   static_cast<int>(minMultiplicity),
                                   static_cast<int>(maxMultiplicity),
                                   NULL    /* version */,
                                   OFTrue  /* doCopyString */,
                                   NULL    /* private creator */));
    }
    else
    {
      // "Private Data Elements have an odd Group Number that is not
      // (0001,eeee), (0003,eeee), (0005,eeee), (0007,eeee), or
      // (FFFF,eeee)."
      if (tag.GetGroup() % 2 == 0 /* even */ ||
          tag.GetGroup() == 0x0001 ||
          tag.GetGroup() == 0x0003 ||
          tag.GetGroup() == 0x0005 ||
          tag.GetGroup() == 0x0007 ||
          tag.GetGroup() == 0xffff)
      {
        char buf[128];
        sprintf(buf, "Trying to register private tag (%04x,%04x), but it must have an odd group >= 0x0009",
                tag.GetGroup(), tag.GetElement());
        throw OrthancException(ErrorCode_ParameterOutOfRange, std::string(buf));
      }

      entry.reset(new DcmDictEntry(tag.GetGroup(),
                                   tag.GetElement(),
                                   evr, name.c_str(),
                                   static_cast<int>(minMultiplicity),
                                   static_cast<int>(maxMultiplicity),
                                   "private" /* version */,
                                   OFTrue    /* doCopyString */,
                                   privateCreator.c_str()));
    }

    entry->setGroupRangeRestriction(DcmDictRange_Unspecified);
    entry->setElementRangeRestriction(DcmDictRange_Unspecified);

    {
      DictionaryLocker locker;

      if (locker->findEntry(DcmTagKey(tag.GetGroup(), tag.GetElement()),
                            privateCreator.empty() ? NULL : privateCreator.c_str()))
      {
        throw OrthancException(ErrorCode_AlreadyExistingTag,
                               "Cannot register twice the tag (" + tag.Format() +
                               "), whose symbolic name is \"" + name + "\"");
      }
      else
      {
        locker->addEntry(entry.release());
      }
    }
  }


  Encoding FromDcmtkBridge::DetectEncoding(bool& hasCodeExtensions,
                                           DcmItem& dataset,
                                           Encoding defaultEncoding)
  {
    // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.12.html#sect_C.12.1.1.2

    OFString tmp;
    if (dataset.findAndGetOFStringArray(DCM_SpecificCharacterSet, tmp).good())
    {
      std::vector<std::string> tokens;
      Toolbox::TokenizeString(tokens, std::string(tmp.c_str()), '\\');

      hasCodeExtensions = (tokens.size() > 1);

      for (size_t i = 0; i < tokens.size(); i++)
      {
        std::string characterSet = Toolbox::StripSpaces(tokens[i]);

        if (!characterSet.empty())
        {
          Encoding encoding;
          
          if (GetDicomEncoding(encoding, characterSet.c_str()))
          {
            // The specific character set is supported by the Orthanc core
            return encoding;
          }
          else
          {
            LOG(WARNING) << "Value of Specific Character Set (0008,0005) is not supported: " << characterSet
                         << ", fallback to ASCII (remove all special characters)";
            return Encoding_Ascii;
          }
        }
      }
    }
    else
    {
      hasCodeExtensions = false;
    }
    
    // No specific character set tag: Use the default encoding
    return defaultEncoding;
  }


  Encoding FromDcmtkBridge::DetectEncoding(DcmItem &dataset,
                                           Encoding defaultEncoding)
  {
    bool hasCodeExtensions;  // ignored
    return DetectEncoding(hasCodeExtensions, dataset, defaultEncoding);
  }


  void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target,
                                            DcmItem& dataset,
                                            unsigned int maxStringLength,
                                            const std::set<DicomTag>& ignoreTagLength)
  {
    const Encoding defaultEncoding = GetDefaultDicomEncoding();
    
    bool hasCodeExtensions;
    Encoding encoding = DetectEncoding(hasCodeExtensions, dataset, defaultEncoding);

    target.Clear();
    for (unsigned long i = 0; i < dataset.card(); i++)
    {
      DcmElement* element = dataset.getElement(i);
      if (element && element->isLeaf())
      {
        target.SetValueInternal(element->getTag().getGTag(),
                                element->getTag().getETag(),
                                ConvertLeafElement(*element, DicomToJsonFlags_Default,
                                                   maxStringLength, encoding, hasCodeExtensions, ignoreTagLength));
      }
      else
      {
        DcmSequenceOfItems* sequence = dynamic_cast<DcmSequenceOfItems*>(element);
        
        if (sequence)
        {
          Json::Value jsonSequence = Json::arrayValue;
          for (unsigned long s = 0; s < sequence->card(); s++)
          {
            DcmItem* child = sequence->getItem(s);
            Json::Value& v = jsonSequence.append(Json::objectValue);
            DatasetToJson(v, *child, DicomToJsonFormat_Full, DicomToJsonFlags_Default, 
                          maxStringLength, encoding, hasCodeExtensions,
                          ignoreTagLength, 1);
          }

          target.SetSequenceValue(DicomTag(element->getTag().getGTag(), element->getTag().getETag()),
                                  jsonSequence);
        }
      }
    }
  }


  DicomTag FromDcmtkBridge::Convert(const DcmTag& tag)
  {
    return DicomTag(tag.getGTag(), tag.getETag());
  }


  DicomTag FromDcmtkBridge::GetTag(const DcmElement& element)
  {
    return DicomTag(element.getGTag(), element.getETag());
  }


  static DicomValue* CreateValueFromUtf8String(const DicomTag& tag,
                                               const std::string& utf8,
                                               unsigned int maxStringLength,
                                               const std::set<DicomTag>& ignoreTagLength)
  {
    if (maxStringLength != 0 &&
        utf8.size() > maxStringLength &&
        ignoreTagLength.find(tag) == ignoreTagLength.end())
    {
      return new DicomValue;  // Too long, create a NULL value
    }
    else
    {
      return new DicomValue(utf8, false);
    }
  }


  DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element,
                                                  DicomToJsonFlags flags,
                                                  unsigned int maxStringLength,
                                                  Encoding encoding,
                                                  bool hasCodeExtensions,
                                                  const std::set<DicomTag>& ignoreTagLength)
  {
    if (!element.isLeaf())
    {
      // This function is only applicable to leaf elements
      throw OrthancException(ErrorCode_BadParameterType);
    }

    {
      char *c = NULL;
      if (element.isaString() &&
          element.getString(c).good())
      {
        if (c == NULL)  // This case corresponds to the empty string
        {
          return new DicomValue("", false);
        }
        else
        {
          const std::string s(c);
          const std::string utf8 = Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions);
          return CreateValueFromUtf8String(GetTag(element), utf8, maxStringLength, ignoreTagLength);
        }
      }
    }


    if (element.getVR() == EVR_UN)
    {
      /**
       * Unknown value representation: Lookup in the dictionary. This
       * is notably the case for private tags registered with the
       * "Dictionary" configuration option, or for public tags with
       * "EVR_UN" in the case of Little Endian Implicit transfer
       * syntax (cf. DICOM CP 246).
       * ftp://medical.nema.org/medical/dicom/final/cp246_ft.pdf
       **/
      DictionaryLocker locker;
      
      const DcmDictEntry* entry = locker->findEntry(element.getTag().getXTag(), 
                                                    element.getTag().getPrivateCreator());
      if (entry != NULL && 
          entry->getVR().isaString())
      {
        Uint8* data = NULL;

        if (element.getUint8Array(data) == EC_Normal)
        {
          Uint32 length = element.getLength();

          if (data == NULL ||
              length == 0)
          {
            return new DicomValue("", false);   // Empty string
          }

          // Remove the trailing padding, if any
          if (length > 0 &&
              length % 2 == 0 &&
              data[length - 1] == '\0')
          {
            length = length - 1;
          }

          if (element.getTag().isPrivate())
          {
            // For private tags, we do not try and convert to UTF-8,
            // as nothing ensures that the encoding of the private tag
            // is the same as that of the remaining of the DICOM
            // dataset. Only go for ASCII strings.
            if (Toolbox::IsAsciiString(data, length))
            {
              const std::string s(reinterpret_cast<const char*>(data), length);
              return CreateValueFromUtf8String(GetTag(element), s, maxStringLength, ignoreTagLength);
            }
            else
            {
              // Not a plain ASCII string: Consider it as a binary
              // value that is handled in the switch-case below
            }
          }
          else
          {
            // For public tags, convert to UTF-8 by using the
            // "SpecificCharacterSet" tag, if present. This branch is
            // new in Orthanc 1.9.1 (cf. DICOM CP 246).
            const std::string s(reinterpret_cast<const char*>(data), length);
            const std::string utf8 = Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions);
            return CreateValueFromUtf8String(GetTag(element), utf8, maxStringLength, ignoreTagLength);
          }
        }
      }
    }

    
    try
    {
      // http://support.dcmtk.org/docs/dcvr_8h-source.html
      switch (element.getVR())
      {

        /**
         * Deal with binary data (including PixelData).
         **/

        case EVR_OB:  // other byte
        case EVR_OF:  // other float
        case EVR_OW:  // other word
        case EVR_UN:  // unknown value representation
        case EVR_ox:  // OB or OW depending on context
        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
        case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
        case EVR_UNKNOWN2B:  // used internally for elements with unknown VR with 2-byte length field in explicit VR
        {
          if (!(flags & DicomToJsonFlags_ConvertBinaryToNull))
          {
            Uint8* data = NULL;
            Uint16* data16 = NULL;
            if (element.getUint8Array(data) == EC_Normal)
            {
              return new DicomValue(reinterpret_cast<const char*>(data), element.getLength(), true);
            }
            else if (element.getUint16Array(data16) == EC_Normal)
            {
              return new DicomValue(reinterpret_cast<const char*>(data16), element.getLength(), true);
            }
          }

          return new DicomValue;
        }
    
        /**
         * Numeric types
         **/ 
      
        case EVR_SL:  // signed long
        {
          return ApplyDcmtkToCTypeConverter<DcmtkToSint32Converter>(element);
        }

        case EVR_SS:  // signed short
        {
          return ApplyDcmtkToCTypeConverter<DcmtkToSint16Converter>(element);
        }

        case EVR_UL:  // unsigned long
        {
          return ApplyDcmtkToCTypeConverter<DcmtkToUint32Converter>(element);
        }

        case EVR_US:  // unsigned short
        {
          return ApplyDcmtkToCTypeConverter<DcmtkToUint16Converter>(element);
        }

        case EVR_FL:  // float single-precision
        {
          return ApplyDcmtkToCTypeConverter<DcmtkToFloat32Converter>(element);
        }

        case EVR_FD:  // float double-precision
        {
          return ApplyDcmtkToCTypeConverter<DcmtkToFloat64Converter>(element);
        }


        /**
         * Attribute tag.
         **/

        case EVR_AT:
        {
          DcmTagKey tag;
          if (dynamic_cast<DcmAttributeTag&>(element).getTagVal(tag, 0).good())
          {
            DicomTag t(tag.getGroup(), tag.getElement());
            return new DicomValue(t.Format(), false);
          }
          else
          {
            return new DicomValue;
          }
        }


        /**
         * Sequence types, should never occur at this point because of
         * "element.isLeaf()".
         **/

        case EVR_SQ:  // sequence of items
          return new DicomValue;


          /**
           * Internal to DCMTK.
           **/ 

        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_PixelData:  // used internally for uncompressed pixeld data
        case EVR_OverlayData:  // used internally for overlay data
          return new DicomValue;


          /**
           * Default case.
           **/ 

        default:
          return new DicomValue;
      }
    }
    catch (boost::bad_lexical_cast&)
    {
      return new DicomValue;
    }
    catch (std::bad_cast&)
    {
      return new DicomValue;
    }
  }


  static Json::Value& PrepareNode(Json::Value& parent,
                                  DcmElement& element,
                                  DicomToJsonFormat format)
  {
    assert(parent.type() == Json::objectValue);

    DicomTag tag(FromDcmtkBridge::GetTag(element));
    const std::string formattedTag = tag.Format();

    if (format == DicomToJsonFormat_Short)
    {
      parent[formattedTag] = Json::nullValue;
      return parent[formattedTag];
    }

    // This code gives access to the name of the private tags
    std::string tagName = FromDcmtkBridge::GetTagName(element);
    
    switch (format)
    {
      case DicomToJsonFormat_Human:
        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 (element.getTag().getPrivateCreator() != NULL)
          {
            node["PrivateCreator"] = element.getTag().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,
                              DicomToJsonFlags flags,
                              unsigned int maxStringLength)
  {
    Json::Value* targetValue = NULL;
    Json::Value* targetType = NULL;

    switch (format)
    {
      case DicomToJsonFormat_Short:
      case DicomToJsonFormat_Human:
      {
        assert(target.type() == Json::nullValue);
        targetValue = &target;
        break;
      }      

      case DicomToJsonFormat_Full:
      {
        assert(target.type() == Json::objectValue);
        target["Value"] = Json::nullValue;
        target["Type"] = Json::nullValue;
        targetType = &target["Type"];
        targetValue = &target["Value"];
        break;
      }

      default:
        throw OrthancException(ErrorCode_ParameterOutOfRange);
    }

    assert(targetValue != NULL);
    assert(targetValue->type() == Json::nullValue);
    assert(targetType == NULL || targetType->type() == Json::nullValue);

    if (value.IsNull())
    {
      if (targetType != NULL)
      {
        *targetType = "Null";
      }
    }
    else if (value.IsBinary())
    {
      if (flags & DicomToJsonFlags_ConvertBinaryToAscii)
      {
        *targetValue = Toolbox::ConvertToAscii(value.GetContent());
      }
      else
      {
        std::string s;
        value.FormatDataUriScheme(s);
        *targetValue = s;
      }

      if (targetType != NULL)
      {
        *targetType = "Binary";
      }
    }
    else if (maxStringLength == 0 ||
             value.GetContent().size() <= maxStringLength)
    {
      *targetValue = value.GetContent();

      if (targetType != NULL)
      {
        *targetType = "String";
      }
    }
    else
    {
      if (targetType != NULL)
      {
        *targetType = "TooLong";
      }
    }
  }                              


  void FromDcmtkBridge::ElementToJson(Json::Value& parent,
                                      DcmElement& element,
                                      DicomToJsonFormat format,
                                      DicomToJsonFlags flags,
                                      unsigned int maxStringLength,
                                      Encoding encoding,
                                      bool hasCodeExtensions,
                                      const std::set<DicomTag>& ignoreTagLength,
                                      unsigned int depth)
  {
    if (parent.type() == Json::nullValue)
    {
      parent = Json::objectValue;
    }

    assert(parent.type() == Json::objectValue);
    Json::Value& target = PrepareNode(parent, element, format);

    if (element.isLeaf())
    {
      // The "0" below lets "LeafValueToJson()" take care of "TooLong" values
      std::unique_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement
                                    (element, flags, 0, encoding, hasCodeExtensions, ignoreTagLength));

      if (ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end())
      {
        LeafValueToJson(target, *v, format, flags, maxStringLength);
      }
      else
      {
        LeafValueToJson(target, *v, format, flags, 0);
      }
    }
    else
    {
      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 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 = target.append(Json::objectValue);
        DatasetToJson(v, *child, format, flags, maxStringLength, encoding, hasCodeExtensions,
                      ignoreTagLength, depth + 1);
      }
    }
  }


  void FromDcmtkBridge::DatasetToJson(Json::Value& parent,
                                      DcmItem& item,
                                      DicomToJsonFormat format,
                                      DicomToJsonFlags flags,
                                      unsigned int maxStringLength,
                                      Encoding encoding,
                                      bool hasCodeExtensions,
                                      const std::set<DicomTag>& ignoreTagLength,
                                      unsigned int depth)
  {
    assert(parent.type() == Json::objectValue);

    for (unsigned long i = 0; i < item.card(); i++)
    {
      DcmElement* element = item.getElement(i);
      if (element == NULL)
      {
        throw OrthancException(ErrorCode_InternalError);
      }

      DicomTag tag(FromDcmtkBridge::Convert(element->getTag()));

      // New flag in Orthanc 1.9.1
      if (depth == 0 &&
          (flags & DicomToJsonFlags_StopAfterPixelData) &&
          tag > DICOM_TAG_PIXEL_DATA)
      {
        continue;
      }

      // New flag in Orthanc 1.9.1
      if ((flags & DicomToJsonFlags_SkipGroupLengths) &&
          tag.GetElement() == 0x0000)
      {
        continue;
      }

      /*element->getTag().isPrivate()*/
      if (tag.IsPrivate() &&
          !(flags & DicomToJsonFlags_IncludePrivateTags))    
      {
        continue;
      }

      if (!(flags & DicomToJsonFlags_IncludeUnknownTags))
      {
        DictionaryLocker locker;
        if (locker->findEntry(element->getTag(), element->getTag().getPrivateCreator()) == NULL)
        {
          continue;
        }
      }

      if (IsBinaryTag(element->getTag()))
      {
        // This is a binary tag
        if ((tag == DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludePixelData)) ||
            (tag != DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludeBinary)))
        {
          continue;
        }
      }

      FromDcmtkBridge::ElementToJson(parent, *element, format, flags, maxStringLength, encoding,
                                     hasCodeExtensions, ignoreTagLength, depth);
    }
  }


  void FromDcmtkBridge::ExtractDicomAsJson(Json::Value& target, 
                                           DcmDataset& dataset,
                                           DicomToJsonFormat format,
                                           DicomToJsonFlags flags,
                                           unsigned int maxStringLength,
                                           const std::set<DicomTag>& ignoreTagLength)
  {
    const Encoding defaultEncoding = GetDefaultDicomEncoding();
    
    bool hasCodeExtensions;
    Encoding encoding = DetectEncoding(hasCodeExtensions, dataset, defaultEncoding);

    target = Json::objectValue;
    DatasetToJson(target, dataset, format, flags, maxStringLength, encoding, hasCodeExtensions, ignoreTagLength, 0);
  }


  void FromDcmtkBridge::ExtractHeaderAsJson(Json::Value& target, 
                                            DcmMetaInfo& dataset,
                                            DicomToJsonFormat format,
                                            DicomToJsonFlags flags,
                                            unsigned int maxStringLength)
  {
    std::set<DicomTag> ignoreTagLength;
    target = Json::objectValue;
    DatasetToJson(target, dataset, format, flags, maxStringLength, Encoding_Ascii, false, ignoreTagLength, 0);
  }


  static std::string GetTagNameInternal(DcmTag& tag)
  {
    if (!hasExternalDictionaries_)
    {
      /**
       * Some patches for important tags because of different DICOM
       * dictionaries between DCMTK versions. Since Orthanc 1.9.4, we
       * don't apply these patches if external dictionaries are
       * loaded, notably for compatibility with DICONDE. In Orthanc <=
       * 1.9.3, this was done by method "DicomTag::GetMainTagsName()".
       **/
      
      DicomTag tmp(tag.getGroup(), tag.getElement());

      if (tmp == DICOM_TAG_ACCESSION_NUMBER)
        return "AccessionNumber";

      if (tmp == DICOM_TAG_SOP_INSTANCE_UID)
        return "SOPInstanceUID";

      if (tmp == DICOM_TAG_PATIENT_ID)
        return "PatientID";

      if (tmp == DICOM_TAG_SERIES_INSTANCE_UID)
        return "SeriesInstanceUID";

      if (tmp == DICOM_TAG_STUDY_INSTANCE_UID)
        return "StudyInstanceUID"; 

      if (tmp == DICOM_TAG_PIXEL_DATA)
        return "PixelData";

      if (tmp == DICOM_TAG_IMAGE_INDEX)
        return "ImageIndex";

      if (tmp == DICOM_TAG_INSTANCE_NUMBER)
        return "InstanceNumber";

      if (tmp == DICOM_TAG_NUMBER_OF_SLICES)
        return "NumberOfSlices";

      if (tmp == DICOM_TAG_NUMBER_OF_FRAMES)
        return "NumberOfFrames";

      if (tmp == DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES)
        return "CardiacNumberOfImages";

      if (tmp == DICOM_TAG_IMAGES_IN_ACQUISITION)
        return "ImagesInAcquisition";

      if (tmp == DICOM_TAG_PATIENT_NAME)
        return "PatientName";

      if (tmp == DICOM_TAG_IMAGE_POSITION_PATIENT)
        return "ImagePositionPatient";

      if (tmp == DICOM_TAG_IMAGE_ORIENTATION_PATIENT)
        return "ImageOrientationPatient";

      // New in Orthanc 1.6.0, as tagged as "RETIRED_" since DCMTK 3.6.4
      if (tmp == DICOM_TAG_OTHER_PATIENT_IDS)
        return "OtherPatientIDs";

      // End of patches
    }

#if 0
    // This version explicitly calls the dictionary
    const DcmDataDictionary& dict = dcmDataDict.rdlock();
    const DcmDictEntry* entry = dict.findEntry(tag, NULL);

    std::string s(DcmTag_ERROR_TagName);
    if (entry != NULL)
    {
      s = std::string(entry->getTagName());
    }

    dcmDataDict.unlock();
    return s;
#else
    const char* name = tag.getTagName();
    if (name == NULL)
    {
      return DcmTag_ERROR_TagName;
    }
    else
    {
      return std::string(name);
    }
#endif
  }


  static bool GetTagFromNameInternal(DicomTag& tag, const std::string& tagName)
  {
    // conversion from old tag names (ex: RETIRED_OtherPatientIDs is the new name for OtherPatientIDs that is still a valid name for DICOM_TAG_OTHER_PATIENT_IDS)
    if (tagName == "OtherPatientIDs")
    {
      tag = DICOM_TAG_OTHER_PATIENT_IDS;
      return true;
    }

    return false;
  }

  std::string FromDcmtkBridge::GetTagName(const DicomTag& t,
                                          const std::string& privateCreator)
  {
    
    DcmTag tag(t.GetGroup(), t.GetElement());

    if (!privateCreator.empty())
    {
      tag.setPrivateCreator(privateCreator.c_str());
    }

    return GetTagNameInternal(tag);
  }


  std::string FromDcmtkBridge::GetTagName(const DcmElement& element)
  {
    // Copy the tag to ensure const-correctness of DcmElement. Note
    // that the private creator information is also copied.
    DcmTag tag(element.getTag());  

    return GetTagNameInternal(tag);
  }

  std::string FromDcmtkBridge::GetTagName(const DicomElement &element)
  {
    return GetTagName(element.GetTag(), "");
  }



  DicomTag FromDcmtkBridge::ParseTag(const char* name)
  {
    DicomTag parsed(0, 0);
    if (DicomTag::ParseHexadecimal(parsed, name))
    {
      return parsed;
    }

#if 0
    const DcmDataDictionary& dict = dcmDataDict.rdlock();
    const DcmDictEntry* entry = dict.findEntry(name);

    if (entry == NULL)
    {
      dcmDataDict.unlock();
      throw OrthancException(ErrorCode_UnknownDicomTag);
    }
    else
    {
      DcmTagKey key = entry->getKey();
      DicomTag tag(key.getGroup(), key.getElement());
      dcmDataDict.unlock();
      return tag;
    }
#else
    DcmTag tag;
    if (DcmTag::findTagFromName(name, tag).good())
    {
      return DicomTag(tag.getGTag(), tag.getETag());
    }
    else
    {
      DicomTag dcmTag(0, 0);
      if (GetTagFromNameInternal(dcmTag, name))
      {
        return dcmTag;
      }

      CLOG(INFO, DICOM) << "Unknown DICOM tag: \"" << name << "\"";
      throw OrthancException(ErrorCode_UnknownDicomTag, name, false);
    }
#endif
  }

  DicomTag FromDcmtkBridge::ParseTag(const std::string &name)
  {
    return ParseTag(name.c_str());
  }

  bool FromDcmtkBridge::HasTag(const DicomMap &fields, const std::string &tagName)
  {
    return fields.HasTag(ParseTag(tagName));
  }

  void FromDcmtkBridge::FormatListOfTags(std::string& output, const std::set<DicomTag>& tags)
  {
    std::set<std::string> values;
    for (std::set<DicomTag>::const_iterator it = tags.begin();
         it != tags.end(); ++it)
    {
      values.insert(it->Format());
    }

    Toolbox::JoinStrings(output, values, ";");
  }

  void FromDcmtkBridge::FormatListOfTags(Json::Value& output, const std::set<DicomTag>& tags)
  {
    output = Json::arrayValue;
    for (std::set<DicomTag>::const_iterator it = tags.begin();
         it != tags.end(); ++it)
    {
      output.append(it->Format());
    }
  }

  // parses a list like "0010,0010;PatientBirthDate;0020,0020"
  void FromDcmtkBridge::ParseListOfTags(std::set<DicomTag>& result, const std::string& source)
  {
    result.clear();

    std::vector<std::string> tokens;
    Toolbox::TokenizeString(tokens, source, ';');

    for (std::vector<std::string>::const_iterator it = tokens.begin();
         it != tokens.end(); ++it)
    {
      if (it->size() > 0)
      {
        DicomTag tag = FromDcmtkBridge::ParseTag(*it);
        result.insert(tag);
      }
    }
  }


  void FromDcmtkBridge::ParseListOfTags(std::set<DicomTag>& result, const Json::Value& source)
  {
    result.clear();

    if (!source.isArray())
    {
      throw OrthancException(ErrorCode_BadRequest, "List of tags is not an array");
    }

    for (Json::ArrayIndex i = 0; i < source.size(); i++)
    {
      const std::string& value = source[i].asString();
      DicomTag tag = FromDcmtkBridge::ParseTag(value);
      result.insert(tag);
    }
  }

  const DicomValue &FromDcmtkBridge::GetValue(const DicomMap &fields,
                                              const std::string &tagName)
  {
    return fields.GetValue(ParseTag(tagName));
  }

  void FromDcmtkBridge::SetValue(DicomMap &target,
                                 const std::string &tagName,
                                 DicomValue *value)
  {
    const DicomTag tag = ParseTag(tagName);
    target.SetValueInternal(tag.GetGroup(), tag.GetElement(), value);
  }


  bool FromDcmtkBridge::IsUnknownTag(const DicomTag& tag)
  {
    DcmTag tmp(tag.GetGroup(), tag.GetElement());
    return tmp.isUnknownVR();
  }


  void FromDcmtkBridge::ToJson(Json::Value& result,
                               const DicomMap& values,
                               DicomToJsonFormat format)
  {
    if (result.type() != Json::objectValue)
    {
      throw OrthancException(ErrorCode_BadParameterType);
    }

    result.clear();

    for (DicomMap::Content::const_iterator 
           it = values.content_.begin(); it != values.content_.end(); ++it)
    {
      switch (format)
      {
        case DicomToJsonFormat_Human:
        {
          // TODO Inject PrivateCreator if some is available in the DicomMap?
          const std::string tagName = GetTagName(it->first, "");

          if (it->second->IsNull())
          {
            result[tagName] = Json::nullValue;
          }
          else if (it->second->IsSequence())
          {
            result[tagName] = Json::arrayValue;
            const Json::Value& jsonSequence = it->second->GetSequenceContent();

            for (Json::Value::ArrayIndex i = 0; i < jsonSequence.size(); ++i)
            {
              Json::Value target = Json::objectValue;
              Toolbox::SimplifyDicomAsJson(target, jsonSequence[i], DicomToJsonFormat_Human);
              result[tagName].append(target);
            }
          }
          else
          {
            // TODO IsBinary
            result[tagName] = it->second->GetContent();
          }
          break;
        }

        case DicomToJsonFormat_Full:
        {
          // TODO Inject PrivateCreator if some is available in the DicomMap?
          const std::string tagName = GetTagName(it->first, "");

          Json::Value value = Json::objectValue;

          value["Name"] = tagName;

          if (it->second->IsNull())
          {
            value["Type"] = "Null";
            value["Value"] = Json::nullValue;
          }
          else if (it->second->IsSequence())
          {
            value["Type"] = "Sequence";
            value["Value"] = it->second->GetSequenceContent();
          }
          else
          {
            // TODO IsBinary
            value["Type"] = "String";
            value["Value"] = it->second->GetContent();
          }

          result[it->first.Format()] = value;
          break;
        }

        case DicomToJsonFormat_Short:
        {
          const std::string hex = it->first.Format();

          if (it->second->IsNull())
          {
            result[hex] = Json::nullValue;
          }
          else if (it->second->IsSequence())
          {
            result[hex] = Json::arrayValue;
            const Json::Value& jsonSequence = it->second->GetSequenceContent();

            for (Json::Value::ArrayIndex i = 0; i < jsonSequence.size(); ++i)
            {
              Json::Value target = Json::objectValue;
              Toolbox::SimplifyDicomAsJson(target, jsonSequence[i], DicomToJsonFormat_Short);
              result[hex].append(target);
            }
          }
          else
          {
            // TODO IsBinary
            result[hex] = it->second->GetContent();
          }

          break;
        }

        default:
          throw OrthancException(ErrorCode_ParameterOutOfRange);
      }
    }
  }


  std::string FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType level)
  {
    char uid[100];

    switch (level)
    {
      case ResourceType_Patient:
        // The "PatientID" field is of type LO (Long String), 64
        // Bytes Maximum. An UUID is of length 36, thus it can be used
        // as a random PatientID.
        return Toolbox::GenerateUuid();

      case ResourceType_Instance:
        return dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT);

      case ResourceType_Series:
        return dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT);

      case ResourceType_Study:
        return dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT);

      default:
        throw OrthancException(ErrorCode_ParameterOutOfRange);
    }
  }


  
  static bool SaveToMemoryBufferInternal(std::string& buffer,
                                         DcmFileFormat& dicom,
                                         E_TransferSyntax xfer,
                                         std::string& errorMessage)
  {
    E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength;

    // Create a memory buffer with the proper size
    {
      const uint32_t estimatedSize = dicom.calcElementLength(xfer, encodingType);  // (*)
      buffer.resize(estimatedSize);
    }

    DcmOutputBufferStream ob(&buffer[0], buffer.size());

    // Fill the memory buffer with the meta-header and the dataset
    dicom.transferInit();
    OFCondition c = dicom.write(ob, xfer, encodingType, NULL,
                                /*opt_groupLength*/ EGL_recalcGL,
                                /*opt_paddingType*/ EPD_noChange,
                                /*padlen*/ 0, /*subPadlen*/ 0, /*instanceLength*/ 0,
                                EWM_updateMeta /* creates new SOP instance UID on lossy */);
    dicom.transferEnd();

    if (c.good())
    {
      // The DICOM file is successfully written, truncate the target
      // buffer if its size was overestimated by (*)
      ob.flush();

      size_t effectiveSize = static_cast<size_t>(ob.tell());
      if (effectiveSize < buffer.size())
      {
        buffer.resize(effectiveSize);
      }

      return true;
    }
    else
    {
      // Error
      buffer.clear();
      errorMessage = std::string(c.text());
      return false;
    }
  }

  bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer,
                                           DcmDataset& dataSet)
  {
    std::string errorMessageNotUsed;
    return SaveToMemoryBuffer(buffer, dataSet, errorMessageNotUsed);
  }



  bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer,
                                           DcmDataset& dataSet,
                                           std::string& errorMessage)
  {
    // Determine the transfer syntax which shall be used to write the
    // information to the file. If not possible, switch to the Little
    // Endian syntax, with explicit length.

    // http://support.dcmtk.org/docs/dcxfer_8h-source.html


    /**
     * Note that up to Orthanc 0.7.1 (inclusive), the
     * "EXS_LittleEndianExplicit" was always used to save the DICOM
     * dataset into memory. We now keep the original transfer syntax
     * (if available).
     **/
    E_TransferSyntax xfer = dataSet.getCurrentXfer();
    if (xfer == EXS_Unknown)
    {
      // No information about the original transfer syntax: This is
      // most probably a DICOM dataset that was read from memory.
      xfer = EXS_LittleEndianExplicit;
    }

    // Create the meta-header information
    DcmFileFormat ff(&dataSet);
    ff.validateMetaInfo(xfer);
    ff.removeInvalidGroups();

    return SaveToMemoryBufferInternal(buffer, ff, xfer, errorMessage);
  }


  bool FromDcmtkBridge::Transcode(DcmFileFormat& dicom,
                                  DicomTransferSyntax syntax,
                                  const DcmRepresentationParameter* representation)
  {
    E_TransferSyntax xfer;
    if (!LookupDcmtkTransferSyntax(xfer, syntax))
    {
      throw OrthancException(ErrorCode_InternalError);
    }
    else
    {
      DicomTransferSyntax sourceSyntax;
      bool known = LookupOrthancTransferSyntax(sourceSyntax, dicom);
      
      if (!dicom.chooseRepresentation(xfer, representation).good() ||
          !dicom.canWriteXfer(xfer) ||
          !dicom.validateMetaInfo(xfer, EWM_updateMeta).good())
      {
        return false;
      }
      else
      {
        dicom.removeInvalidGroups();

        if (known)
        {
          CLOG(INFO, DICOM) << "Transcoded an image from transfer syntax "
                            << GetTransferSyntaxUid(sourceSyntax) << " to "
                            << GetTransferSyntaxUid(syntax);
        }
        else
        {
          CLOG(INFO, DICOM) << "Transcoded an image from unknown transfer syntax to "
                            << GetTransferSyntaxUid(syntax);
        }
        
        return true;
      }
    }
  }


  ValueRepresentation FromDcmtkBridge::LookupValueRepresentation(const DicomTag& tag)
  {
    DcmTag t(tag.GetGroup(), tag.GetElement());
    return Convert(t.getEVR());
  }

  ValueRepresentation FromDcmtkBridge::Convert(const DcmEVR vr)
  {
    switch (vr)
    {
      case EVR_AE:
        return ValueRepresentation_ApplicationEntity;

      case EVR_AS:
        return ValueRepresentation_AgeString;

      case EVR_AT:
        return ValueRepresentation_AttributeTag;

      case EVR_CS:
        return ValueRepresentation_CodeString;

      case EVR_DA:
        return ValueRepresentation_Date;

      case EVR_DS:
        return ValueRepresentation_DecimalString;

      case EVR_DT:
        return ValueRepresentation_DateTime;

      case EVR_FL:
        return ValueRepresentation_FloatingPointSingle;

      case EVR_FD:
        return ValueRepresentation_FloatingPointDouble;

      case EVR_IS:
        return ValueRepresentation_IntegerString;

      case EVR_LO:
        return ValueRepresentation_LongString;

      case EVR_LT:
        return ValueRepresentation_LongText;

      case EVR_OB:
        return ValueRepresentation_OtherByte;

#if DCMTK_VERSION_NUMBER >= 361
      case EVR_OD:
        return ValueRepresentation_OtherDouble;
#endif

      case EVR_OF:
        return ValueRepresentation_OtherFloat;

#if DCMTK_VERSION_NUMBER >= 362
      case EVR_OL:
        return ValueRepresentation_OtherLong;
#endif

      case EVR_OW:
        return ValueRepresentation_OtherWord;

      case EVR_PN:
        return ValueRepresentation_PersonName;

      case EVR_SH:
        return ValueRepresentation_ShortString;

      case EVR_SL:
        return ValueRepresentation_SignedLong;

      case EVR_SQ:
        return ValueRepresentation_Sequence;

      case EVR_SS:
        return ValueRepresentation_SignedShort;

      case EVR_ST:
        return ValueRepresentation_ShortText;

      case EVR_TM:
        return ValueRepresentation_Time;

#if DCMTK_VERSION_NUMBER >= 361
      case EVR_UC:
        return ValueRepresentation_UnlimitedCharacters;
#endif

      case EVR_UI:
        return ValueRepresentation_UniqueIdentifier;

      case EVR_UL:
        return ValueRepresentation_UnsignedLong;

      case EVR_UN:
        return ValueRepresentation_Unknown;

#if DCMTK_VERSION_NUMBER >= 361
      case EVR_UR:
        return ValueRepresentation_UniversalResource;
#endif

      case EVR_US:
        return ValueRepresentation_UnsignedShort;

      case EVR_UT:
        return ValueRepresentation_UnlimitedText;

      default:
        return ValueRepresentation_NotSupported;
    }
  }


  DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag,
                                                   const std::string& privateCreator)
  {
    if (tag.IsPrivate() &&
        privateCreator.empty())
    {
      // This solves issue 140 (Modifying private tags with REST API
      // changes VR from LO to UN)
      // https://orthanc.uclouvain.be/bugs/show_bug.cgi?id=140
      LOG(WARNING) << "Private creator should not be empty while creating a private tag: " << tag.Format();
    }
    
#if DCMTK_VERSION_NUMBER >= 362
    DcmTag key(tag.GetGroup(), tag.GetElement());
    if (tag.IsPrivate())
    {
      return DcmItem::newDicomElement(key, privateCreator.c_str());
    }
    else
    {
      return DcmItem::newDicomElement(key, NULL);
    }
    
#else
    DcmTag key(tag.GetGroup(), tag.GetElement());
    if (tag.IsPrivate())
    {
      // https://forum.dcmtk.org/viewtopic.php?t=4527
      LOG(WARNING) << "You are using DCMTK <= 3.6.1: All the private tags "
        "are considered as having a binary value representation";
      key.setPrivateCreator(privateCreator.c_str());
      return new DcmOtherByteOtherWord(key);
    }
    else
    {
      return newDicomElement(key);
    }
#endif      
  }



  void FromDcmtkBridge::FillElementWithString(DcmElement& element,
                                              const std::string& utf8Value,
                                              bool decodeDataUriScheme,
                                              Encoding dicomEncoding)
  {
    std::string binary;
    const std::string* decoded = &utf8Value;

    if (decodeDataUriScheme &&
        boost::starts_with(utf8Value, URI_SCHEME_PREFIX_BINARY))
    {
      std::string mime;
      if (!Toolbox::DecodeDataUriScheme(mime, binary, utf8Value))
      {
        throw OrthancException(ErrorCode_BadFileFormat);
      }

      decoded = &binary;
    }
    else if (dicomEncoding != Encoding_Utf8)
    {
      binary = Toolbox::ConvertFromUtf8(utf8Value, dicomEncoding);
      decoded = &binary;
    }

    if (IsBinaryTag(element.getTag()))
    {
      bool ok;

      switch (element.getTag().getEVR())
      {
        case EVR_OW:
          if (decoded->size() % sizeof(Uint16) != 0)
          {
            LOG(ERROR) << "A tag with OW VR must have an even number of bytes";
            ok = false;
          }
          else
          {
            ok = element.putUint16Array((const Uint16*) decoded->c_str(), decoded->size() / sizeof(Uint16)).good();
          }
          
          break;
      
        default:
          ok = element.putUint8Array((const Uint8*) decoded->c_str(), decoded->size()).good();
          break;
      }
      
      if (ok)
      {
        return;
      }
      else
      {
        throw OrthancException(ErrorCode_InternalError);
      }
    }

    bool ok = false;
    
    try
    {
      switch (element.getTag().getEVR())
      {
        // http://support.dcmtk.org/docs/dcvr_8h-source.html

        /**
         * TODO.
         **/

        case EVR_OB:  // other byte
        case EVR_OW:  // other word
          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
#if DCMTK_VERSION_NUMBER >= 361
        case EVR_UC:  // unlimited characters
        case EVR_UR:  // URI/URL
#endif
        {
          ok = element.putString(decoded->c_str()).good();
          break;
        }

        
        /**
         * Numerical types
         **/ 
      
        case EVR_SL:  // signed long
        {
          if (decoded->find('\\') != std::string::npos)
          {
            ok = element.putString(decoded->c_str()).good();
          }
          else
          {
            ok = element.putSint32(boost::lexical_cast<Sint32>(*decoded)).good();
          }
          break;
        }

        case EVR_SS:  // signed short
        {
          if (decoded->find('\\') != std::string::npos)
          {
            ok = element.putString(decoded->c_str()).good();
          }
          else
          {
            ok = element.putSint16(boost::lexical_cast<Sint16>(*decoded)).good();
          }
          break;
        }

        case EVR_UL:  // unsigned long
#if DCMTK_VERSION_NUMBER >= 362
        case EVR_OL:  // other long (requires byte-swapping)
#endif
        {
          if (decoded->find('\\') != std::string::npos)
          {
            ok = element.putString(decoded->c_str()).good();
          }
          else
          {
            ok = element.putUint32(boost::lexical_cast<Uint32>(*decoded)).good();
          }
          break;
        }

        case EVR_xs: // unsigned short, signed short or multiple values
        {
          if (decoded->find('\\') != std::string::npos)
          {
            ok = element.putString(decoded->c_str()).good();
          }
          else if (decoded->find('-') != std::string::npos)
          {
            ok = element.putSint16(boost::lexical_cast<Sint16>(*decoded)).good();
          }
          else
          {
            ok = element.putUint16(boost::lexical_cast<Uint16>(*decoded)).good();  
          }
          break;
        }

        case EVR_US:  // unsigned short
        {
          if (decoded->find('\\') != std::string::npos)
          {
            ok = element.putString(decoded->c_str()).good();
          }
          else
          {
            ok = element.putUint16(boost::lexical_cast<Uint16>(*decoded)).good();
          }
          break;
        }

        case EVR_FL:  // float single-precision
        case EVR_OF:  // other float (requires byte swapping)
        {
          if (decoded->find('\\') != std::string::npos)
          {
            ok = element.putString(decoded->c_str()).good();
          }
          else
          {
            ok = element.putFloat32(boost::lexical_cast<float>(*decoded)).good();
          }
          break;
        }

        case EVR_FD:  // float double-precision
#if DCMTK_VERSION_NUMBER >= 361
        case EVR_OD:  // other double (requires byte-swapping)
#endif
        {
          if (decoded->find('\\') != std::string::npos)
          {
            ok = element.putString(decoded->c_str()).good();
          }
          else
          {
            ok = element.putFloat64(boost::lexical_cast<double>(*decoded)).good();
          }
          break;
        }


        /**
         * Other types
         **/
        
        case EVR_AT:  // attribute tag, new in Orthanc 1.9.4
        {
          DicomTag value = ParseTag(utf8Value);
          ok = element.putTagVal(DcmTagKey(value.GetGroup(), value.GetElement())).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_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)
    {
      DicomTag tag(element.getTag().getGroup(), element.getTag().getElement());
      throw OrthancException(ErrorCode_BadFileFormat,
                             "While creating a DICOM instance, tag (" + tag.Format() +
                             ") has out-of-range value: \"" + (*decoded) + "\"");
    }
  }


  DcmElement* FromDcmtkBridge::FromJson(const DicomTag& tag,
                                        const Json::Value& value,
                                        bool decodeDataUriScheme,
                                        Encoding dicomEncoding,
                                        const std::string& privateCreator)
  {
    std::unique_ptr<DcmElement> element;

    switch (value.type())
    {
      case Json::stringValue:
        element.reset(CreateElementForTag(tag, privateCreator));
        FillElementWithString(*element, value.asString(), decodeDataUriScheme, dicomEncoding);
        break;

      case Json::nullValue:
        element.reset(CreateElementForTag(tag, privateCreator));
        FillElementWithString(*element, "", decodeDataUriScheme, dicomEncoding);
        break;

      case Json::arrayValue:
      {
        const char* p = NULL;
        if (tag.IsPrivate() &&
            !privateCreator.empty())
        {
          p = privateCreator.c_str();
        }
        
        DcmTag key(tag.GetGroup(), tag.GetElement(), p);
        if (key.getEVR() != EVR_SQ)
        {
          throw OrthancException(ErrorCode_BadParameterType,
                                 "Bad Parameter type for tag " + tag.Format());
        }

        DcmSequenceOfItems* sequence = new DcmSequenceOfItems(key);
        element.reset(sequence);
        
        for (Json::Value::ArrayIndex i = 0; i < value.size(); i++)
        {
          std::unique_ptr<DcmItem> item(new DcmItem);

          switch (value[i].type())
          {
            case Json::objectValue:
            {
              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]], decodeDataUriScheme, dicomEncoding, privateCreator));
              }
              break;
            }

            case Json::arrayValue:
            {
              // Lua cannot disambiguate between an empty dictionary
              // and an empty array
              if (value[i].size() != 0)
              {
                throw OrthancException(ErrorCode_BadParameterType);
              }
              break;
            }

            default:
              throw OrthancException(ErrorCode_BadParameterType);
          }

          sequence->append(item.release());
        }

        break;
      }

      default:
        throw OrthancException(ErrorCode_BadParameterType, "Bad Parameter type for tag " + tag.Format());
    }

    return element.release();
  }


  DcmPixelSequence* FromDcmtkBridge::GetPixelSequence(DcmDataset& dataset)
  {
    DcmElement *element = NULL;
    if (!dataset.findAndGetElement(DCM_PixelData, element).good())
    {
      throw OrthancException(ErrorCode_BadFileFormat);
    }

    DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element);

    E_TransferSyntax repType;
    const DcmRepresentationParameter *repParam = NULL;
    pixelData.getCurrentRepresentationKey(repType, repParam);
    
    DcmPixelSequence* pixelSequence = NULL;
    if (!pixelData.getEncapsulatedRepresentation(repType, repParam, pixelSequence).good())
    {
      return NULL;
    }
    else
    {
      return pixelSequence;
    }
  }


  Encoding FromDcmtkBridge::ExtractEncoding(const Json::Value& json,
                                            Encoding defaultEncoding)
  {
    if (json.type() != Json::objectValue)
    {
      throw OrthancException(ErrorCode_BadParameterType);
    }

    Encoding encoding = defaultEncoding;

    const Json::Value::Members tags = json.getMemberNames();
    
    // Look for SpecificCharacterSet (0008,0005) in the JSON file
    for (size_t i = 0; i < tags.size(); i++)
    {
      DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]);
      if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET)
      {
        const Json::Value& value = json[tags[i]];
        if (value.type() != Json::stringValue ||
            (value.asString().length() != 0 &&
             !GetDicomEncoding(encoding, value.asCString())))
        {
          throw OrthancException(ErrorCode_BadRequest,
                                 "Unknown encoding while creating DICOM from JSON: " +
                                 value.toStyledString());
        }

        if (value.asString().length() == 0)
        {
          return defaultEncoding;
        }
      }
    }

    return encoding;
  } 


  static void SetString(DcmDataset& target,
                        const DcmTag& tag,
                        const std::string& value)
  {
    if (!target.putAndInsertString(tag, value.c_str()).good())
    {
      throw OrthancException(ErrorCode_InternalError);
    }
  }


  DcmDataset* FromDcmtkBridge::FromJson(const Json::Value& json,  // Encoded using UTF-8
                                        bool generateIdentifiers,
                                        bool decodeDataUriScheme,
                                        Encoding defaultEncoding,
                                        const std::string& privateCreator)
  {
    std::unique_ptr<DcmDataset> result(new DcmDataset);
    Encoding encoding = ExtractEncoding(json, defaultEncoding);

    SetString(*result, DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(encoding));

    const Json::Value::Members tags = json.getMemberNames();
    
    bool hasPatientId = false;
    bool hasStudyInstanceUid = false;
    bool hasSeriesInstanceUid = false;
    bool hasSopInstanceUid = false;

    for (size_t i = 0; i < tags.size(); i++)
    {
      DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]);
      const Json::Value& value = json[tags[i]];

      if (tag == DICOM_TAG_PATIENT_ID)
      {
        hasPatientId = true;
      }
      else if (tag == DICOM_TAG_STUDY_INSTANCE_UID)
      {
        hasStudyInstanceUid = true;
      }
      else if (tag == DICOM_TAG_SERIES_INSTANCE_UID)
      {
        hasSeriesInstanceUid = true;
      }
      else if (tag == DICOM_TAG_SOP_INSTANCE_UID)
      {
        hasSopInstanceUid = true;
      }

      if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET)
      {
        std::unique_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator));

        result->findAndDeleteElement(element->getTag());

        DcmElement* tmp = element.release();
        if (!result->insert(tmp, false, false).good())
        {
          delete tmp;
          throw OrthancException(ErrorCode_InternalError);
        }
      }
    }

    if (!hasPatientId &&
        generateIdentifiers)
    {
      SetString(*result, DCM_PatientID, GenerateUniqueIdentifier(ResourceType_Patient));
    }

    if (!hasStudyInstanceUid &&
        generateIdentifiers)
    {
      SetString(*result, DCM_StudyInstanceUID, GenerateUniqueIdentifier(ResourceType_Study));
    }

    if (!hasSeriesInstanceUid &&
        generateIdentifiers)
    {
      SetString(*result, DCM_SeriesInstanceUID, GenerateUniqueIdentifier(ResourceType_Series));
    }

    if (!hasSopInstanceUid &&
        generateIdentifiers)
    {
      SetString(*result, DCM_SOPInstanceUID, GenerateUniqueIdentifier(ResourceType_Instance));
    }

    return result.release();
  }


  DcmFileFormat* FromDcmtkBridge::LoadFromMemoryBuffer(const void* buffer,
                                                       size_t size)
  {
    DcmInputBufferStream is;
    if (size > 0)
    {
      is.setBuffer(buffer, size);
    }
    is.setEos();

    std::unique_ptr<DcmFileFormat> result(new DcmFileFormat);

    result->transferInit();

    /**
     * New in Orthanc 1.6.0: The "size" is given as an argument to the
     * "read()" method. This can avoid huge memory consumption if
     * parsing an invalid DICOM file, which can notably been observed
     * by executing the integration test "test_upload_compressed" on
     * valgrind running Orthanc.
     **/
    if (!result->read(is, EXS_Unknown, EGL_noChange, size).good())
    {
      throw OrthancException(ErrorCode_BadFileFormat,
                             "Cannot parse an invalid DICOM file (size: " +
                             boost::lexical_cast<std::string>(size) + " bytes)");
    }

    result->loadAllDataIntoMemory();
    result->transferEnd();

    return result.release();
  }


  void FromDcmtkBridge::FromJson(DicomMap& target,
                                 const Json::Value& source,
                                 const char* fieldName)
  {
    if (source.type() != Json::objectValue)
    {
      if (fieldName != NULL) 
      {
        throw OrthancException(ErrorCode_BadFileFormat, std::string("Expecting an object in field '") + std::string(fieldName) + std::string("'"));
      }
      else
      {
        throw OrthancException(ErrorCode_BadFileFormat, "Expecting an object");
      }
    }

    target.Clear();

    Json::Value::Members members = source.getMemberNames();

    for (size_t i = 0; i < members.size(); i++)
    {
      const Json::Value& value = source[members[i]];

      if (value.type() != Json::stringValue)
      {
        throw OrthancException(ErrorCode_BadFileFormat, std::string("Expecting a string in field '") + members[i] + std::string("'"));
      }
      
      target.SetValue(ParseTag(members[i]), value.asString(), false);
    }
  }




  void FromDcmtkBridge::ChangeStringEncoding(DcmItem& dataset,
                                             Encoding source,
                                             bool hasSourceCodeExtensions,
                                             Encoding target)
  {
    // Recursive exploration of a dataset to change the encoding of
    // each string-like element

    if (source == target)
    {
      return;
    }

    for (unsigned long i = 0; i < dataset.card(); i++)
    {
      DcmElement* element = dataset.getElement(i);
      if (element)
      {
        if (element->isLeaf())
        {
          char *c = NULL;
          if (element->isaString() &&
              element->getString(c).good() && 
              c != NULL)
          {
            std::string a = Toolbox::ConvertToUtf8(c, source, hasSourceCodeExtensions);
            std::string b = Toolbox::ConvertFromUtf8(a, target);
            element->putString(b.c_str());
          }
        }
        else
        {
          // "All subclasses of DcmElement except for DcmSequenceOfItems
          // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset
          // etc. are not." The following dynamic_cast is thus OK.
          DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element);

          for (unsigned long j = 0; j < sequence.card(); j++)
          {
            ChangeStringEncoding(*sequence.getItem(j), source, hasSourceCodeExtensions, target);
          }
        }
      }
    }
  }


  void FromDcmtkBridge::InitializeCodecs()
  {
#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
    CLOG(INFO, DICOM) << "Registering JPEG Lossless codecs in DCMTK";
    DJLSDecoderRegistration::registerCodecs();
# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
    DJLSEncoderRegistration::registerCodecs();
# endif
#endif

#if ORTHANC_ENABLE_DCMTK_JPEG == 1
    CLOG(INFO, DICOM) << "Registering JPEG codecs in DCMTK";
    DJDecoderRegistration::registerCodecs();
# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
    DJEncoderRegistration::registerCodecs();
# endif
#endif

    CLOG(INFO, DICOM) << "Registering RLE codecs in DCMTK";
    DcmRLEDecoderRegistration::registerCodecs(); 
#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
    DcmRLEEncoderRegistration::registerCodecs();
#endif
  }


  void FromDcmtkBridge::FinalizeCodecs()
  {
#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
    // Unregister JPEG-LS codecs
    DJLSDecoderRegistration::cleanup();
# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
    DJLSEncoderRegistration::cleanup();
# endif
#endif

#if ORTHANC_ENABLE_DCMTK_JPEG == 1
    // Unregister JPEG codecs
    DJDecoderRegistration::cleanup();
# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
    DJEncoderRegistration::cleanup();
# endif
#endif

    DcmRLEDecoderRegistration::cleanup(); 
#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
    DcmRLEEncoderRegistration::cleanup();
#endif
  }



  // Forward declaration
  static bool ApplyVisitorToElement(DcmElement& element,
                                    ITagVisitor& visitor,
                                    const std::vector<DicomTag>& parentTags,
                                    const std::vector<size_t>& parentIndexes,
                                    Encoding encoding,
                                    bool hasCodeExtensions);
 
  static void ApplyVisitorToDataset(DcmItem& dataset,
                                    ITagVisitor& visitor,
                                    const std::vector<DicomTag>& parentTags,
                                    const std::vector<size_t>& parentIndexes,
                                    Encoding encoding,
                                    bool hasCodeExtensions)
  {
    assert(parentTags.size() == parentIndexes.size());

    std::set<DcmTagKey> toRemove;
    
    for (unsigned long i = 0; i < dataset.card(); i++)
    {
      DcmElement* element = dataset.getElement(i);
      if (element == NULL)
      {
        throw OrthancException(ErrorCode_InternalError);
      }
      else
      {
        if (!ApplyVisitorToElement(*element, visitor, parentTags, parentIndexes, encoding, hasCodeExtensions))
        {
          toRemove.insert(element->getTag());
        }
      }      
    }

    // Remove all the tags that were planned for removal (cf. ITagVisitor::Action_Remove)
    for (std::set<DcmTagKey>::const_iterator
           it = toRemove.begin(); it != toRemove.end(); ++it)
    {
      std::unique_ptr<DcmElement> tmp(dataset.remove(*it));
    }
  }


  // Returns "true" iff the element must be kept. If "false" is
  // returned, the element will be removed.
  static bool ApplyVisitorToLeaf(DcmElement& element,
                                 ITagVisitor& visitor,
                                 const std::vector<DicomTag>& parentTags,
                                 const std::vector<size_t>& parentIndexes,
                                 const DicomTag& tag,
                                 Encoding encoding,
                                 bool hasCodeExtensions)
  {
    // TODO - Merge this function, that is more recent, with ConvertLeafElement()

    assert(element.isLeaf());

    DcmEVR evr = element.getTag().getEVR();

    
    /**
     * Fix the EVR for types internal to DCMTK 
     **/

    if (evr == EVR_ox)  // OB or OW depending on context
    {
      evr = EVR_OB;
    }
    else if (evr == EVR_xs) // SS or US depending on context
    {
      // So far we assume that it's alway US (as a best guess: https://forum.dcmtk.org/viewtopic.php?t=932)
      // However, e.g. in a LUTDescriptor (3 values), the middle value can be a SS depending on other tag values while first and third value are always US.
      // This patch, although not perfect fixes  https://orthanc.uclouvain.be/bugs/show_bug.cgi?id=214.
      // It might need some rework once we encounter a LUTDescriptor with a SS value. ref: https://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.11.2.html#sect_C.11.2.1.1
      evr = EVR_US;  
    }
    else if (evr == EVR_lt) // US, SS or OW depending on context, used for LUT Data (thus the name)
    {
      // best guess is OW: final user should be able to interpret it correctly depending on the context
      evr = EVR_OW;      
    }

    if (evr == EVR_UNKNOWN ||  // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
        evr == EVR_UNKNOWN2B)  // used internally for elements with unknown VR with 2-byte length field in explicit VR
    {
      evr = EVR_UN;
    }

    if (evr == EVR_UN)
    {
      // New in Orthanc 1.9.5
      DictionaryLocker locker;
      
      const DcmDictEntry* entry = locker->findEntry(element.getTag().getXTag(),
                                                    element.getTag().getPrivateCreator());

      if (entry != NULL)
      {
        evr = entry->getEVR();
      }
    }

    const ValueRepresentation vr = FromDcmtkBridge::Convert(evr);

    
    /**
     * Deal with binary data (including PixelData).
     **/

    if (evr == EVR_OB ||  // other byte
        evr == EVR_OW ||  // other word
        evr == EVR_UN)    // unknown value representation
    {
      Uint16* data16 = NULL;
      Uint8* data = NULL;

      ITagVisitor::Action action;
      
      if ((element.getTag() == DCM_PixelData ||  // (*) New in Orthanc 1.9.1
           evr == EVR_OW) &&
          element.getUint16Array(data16) == EC_Normal)
      {
        action = visitor.VisitBinary(parentTags, parentIndexes, tag, vr, data16, element.getLength());
      }
      else if (evr != EVR_OW &&
               element.getUint8Array(data) == EC_Normal)
      {
        /**
         * WARNING: The call to "getUint8Array()" crashes
         * (segmentation fault) on big-endian architectures if applied
         * to pixel data, during the call to "swapIfNecessary()" in
         * "DcmPolymorphOBOW::getUint8Array()" (this method is not
         * reimplemented in derived class "DcmPixelData"). However,
         * "getUint16Array()" works correctly, hence (*).
         **/
        action = visitor.VisitBinary(parentTags, parentIndexes, tag, vr, data, element.getLength());
      }
      else
      {
        action = visitor.VisitNotSupported(parentTags, parentIndexes, tag, vr);
      }

      switch (action)
      {
        case ITagVisitor::Action_None:
          return true;  // We're done

        case ITagVisitor::Action_Remove:
          return false;

        case ITagVisitor::Action_Replace:
          throw OrthancException(ErrorCode_NotImplemented, "Iterator cannot replace binary data");

        default:
          throw OrthancException(ErrorCode_ParameterOutOfRange);
      }
    }


    /**
     * Deal with plain strings (and convert them to UTF-8)
     **/

    char *c = NULL;
    if (element.isaString() &&
        element.getString(c).good())
    {
      std::string utf8;

      if (c != NULL)  // This case corresponds to the empty string
      {
        if (element.getTag() == DCM_SpecificCharacterSet)
        {
          utf8.assign(c);
        }
        else
        {
          std::string s(c);
          utf8 = Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions);
        }
      }

      std::string newValue;
      ITagVisitor::Action action = visitor.VisitString
        (newValue, parentTags, parentIndexes, tag, vr, utf8);

      switch (action)
      {
        case ITagVisitor::Action_None:
          return true;

        case ITagVisitor::Action_Remove:
          return false;

        case ITagVisitor::Action_Replace:
        {
          std::string s = Toolbox::ConvertFromUtf8(newValue, encoding);
          if (element.putString(s.c_str()) != EC_Normal)
          {
            throw OrthancException(ErrorCode_InternalError,
                                   "Iterator cannot replace value of tag: " + tag.Format());
          }

          return true;
        }

        default:
          throw OrthancException(ErrorCode_InternalError);
      }
    }


    ITagVisitor::Action action;
    
    try
    {
      // http://support.dcmtk.org/docs/dcvr_8h-source.html
      switch (evr)
      {

        /**
         * Plain string values.
         **/

        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
        {
          Uint8* data = NULL;
          
          if (element.getUint8Array(data) == EC_Normal)
          {
            const Uint32 length = element.getLength();
            Uint32 l = 0;
            while (l < length &&
                   data[l] != 0)
            {
              l++;
            }

            std::string ignored;
            std::string s(reinterpret_cast<const char*>(data), l);
            action = visitor.VisitString(ignored, parentTags, parentIndexes, tag, vr,
                                         Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions));
          }
          else
          {
            action = visitor.VisitNotSupported(parentTags, parentIndexes, tag, vr);
          }

          if (action == ITagVisitor::Action_Replace)
          {
            LOG(WARNING) << "Iterator cannot replace this string tag: "
                         << FromDcmtkBridge::GetTagName(element)
                         << " (" << tag.Format() << ")";
            return true;
          }

          break;
        }
    
        /**
         * Numeric types
         **/ 
      
        case EVR_SL:  // signed long
        {
          DcmSignedLong& content = dynamic_cast<DcmSignedLong&>(element);

          std::vector<int64_t> values;
          values.reserve(content.getVM());

          for (unsigned long i = 0; i < content.getVM(); i++)
          {
            Sint32 f;
            if (content.getSint32(f, i).good())
            {
              values.push_back(f);
            }
          }

          action = visitor.VisitIntegers(parentTags, parentIndexes, tag, vr, values);
          break;
        }

        case EVR_SS:  // signed short
        {
          DcmSignedShort& content = dynamic_cast<DcmSignedShort&>(element);

          std::vector<int64_t> values;
          values.reserve(content.getVM());

          for (unsigned long i = 0; i < content.getVM(); i++)
          {
            Sint16 f;
            if (content.getSint16(f, i).good())
            {
              values.push_back(f);
            }
          }

          action = visitor.VisitIntegers(parentTags, parentIndexes, tag, vr, values);
          break;
        }

        case EVR_UL:  // unsigned long
#if DCMTK_VERSION_NUMBER >= 362
        case EVR_OL:
#endif
        {
          DcmUnsignedLong& content = dynamic_cast<DcmUnsignedLong&>(element);

          std::vector<int64_t> values;
          values.reserve(content.getVM());

          for (unsigned long i = 0; i < content.getVM(); i++)
          {
            Uint32 f;
            if (content.getUint32(f, i).good())
            {
              values.push_back(f);
            }
          }

          action = visitor.VisitIntegers(parentTags, parentIndexes, tag, vr, values);
          break;
        }

        case EVR_US:  // unsigned short
        {
          DcmUnsignedShort& content = dynamic_cast<DcmUnsignedShort&>(element);

          std::vector<int64_t> values;
          values.reserve(content.getVM());

          for (unsigned long i = 0; i < content.getVM(); i++)
          {
            Uint16 f;
            if (content.getUint16(f, i).good())
            {
              values.push_back(f);
            }
          }

          action = visitor.VisitIntegers(parentTags, parentIndexes, tag, vr, values);
          break;
        }

        case EVR_FL:  // float single-precision
        case EVR_OF:
        {
          DcmFloatingPointSingle& content = dynamic_cast<DcmFloatingPointSingle&>(element);

          std::vector<double> values;
          values.reserve(content.getVM());

          for (unsigned long i = 0; i < content.getVM(); i++)
          {
            Float32 f;
            if (content.getFloat32(f, i).good())
            {
              values.push_back(f);
            }
          }

          action = visitor.VisitDoubles(parentTags, parentIndexes, tag, vr, values);
          break;
        }

        case EVR_FD:  // float double-precision
#if DCMTK_VERSION_NUMBER >= 361
        case EVR_OD:
#endif
        {
          DcmFloatingPointDouble& content = dynamic_cast<DcmFloatingPointDouble&>(element);

          std::vector<double> values;
          values.reserve(content.getVM());

          for (unsigned long i = 0; i < content.getVM(); i++)
          {
            Float64 f;
            if (content.getFloat64(f, i).good())
            {
              values.push_back(f);
            }
          }

          action = visitor.VisitDoubles(parentTags, parentIndexes, tag, vr, values);
          break;
        }


        /**
         * Attribute tag.
         **/

        case EVR_AT:
        {
          DcmAttributeTag& content = dynamic_cast<DcmAttributeTag&>(element);

          std::vector<DicomTag> values;
          values.reserve(content.getVM());

          for (unsigned long i = 0; i < content.getVM(); i++)
          {
            DcmTagKey f;
            if (content.getTagVal(f, i).good())
            {
              DicomTag t(f.getGroup(), f.getElement());
              values.push_back(t);
            }
          }

          assert(vr == ValueRepresentation_AttributeTag);
          action = visitor.VisitAttributes(parentTags, parentIndexes, tag, values);
          break;
        }


        /**
         * Sequence types, should never occur at this point because of
         * "element.isLeaf()".
         **/

        case EVR_SQ:  // sequence of items
        {
          return true;
        }
        
        
        /**
         * Internal to DCMTK.
         **/ 

        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_PixelData:  // used internally for uncompressed pixeld data
        case EVR_OverlayData:  // used internally for overlay data
        {
          action = visitor.VisitNotSupported(parentTags, parentIndexes, tag, vr);
          break;
        }
        

        /**
         * Default case.
         **/ 

        default:
          return true;
      }

      switch (action)
      {
        case ITagVisitor::Action_None:
          return true;  // We're done

        case ITagVisitor::Action_Remove:
          return false;

        case ITagVisitor::Action_Replace:
          throw OrthancException(ErrorCode_NotImplemented, "Iterator cannot replace non-string-like data");

        default:
          throw OrthancException(ErrorCode_ParameterOutOfRange);
      }
    }
    catch (boost::bad_lexical_cast&)
    {
      return true;
    }
    catch (std::bad_cast&)
    {
      return true;
    }
  }


  // Returns "true" iff the element must be kept. If "false" is
  // returned, the element will be removed.
  static bool ApplyVisitorToElement(DcmElement& element,
                                    ITagVisitor& visitor,
                                    const std::vector<DicomTag>& parentTags,
                                    const std::vector<size_t>& parentIndexes,
                                    Encoding encoding,
                                    bool hasCodeExtensions)
  {
    assert(parentTags.size() == parentIndexes.size());

    DicomTag tag(FromDcmtkBridge::Convert(element.getTag()));

    if (element.isLeaf())
    {
      return ApplyVisitorToLeaf(element, visitor, parentTags, parentIndexes, tag, encoding, hasCodeExtensions);
    }
    else
    {
      // "All subclasses of DcmElement except for DcmSequenceOfItems
      // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset
      // etc. are not." The following dynamic_cast is thus OK.
      DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(element);

      ITagVisitor::Action action = visitor.VisitSequence(parentTags, parentIndexes, tag, sequence.card());

      switch (action)
      {
        case ITagVisitor::Action_None:
          if (sequence.card() != 0)  // Minor optimization to avoid creating "tags" and "indexes" if not needed
          {
            std::vector<DicomTag> tags = parentTags;
            std::vector<size_t> indexes = parentIndexes;
            tags.push_back(tag);
            indexes.push_back(0);

            for (unsigned long i = 0; i < sequence.card(); i++)
            {
              indexes.back() = static_cast<size_t>(i);
              DcmItem* child = sequence.getItem(i);
              ApplyVisitorToDataset(*child, visitor, tags, indexes, encoding, hasCodeExtensions);
            }
          }

          return true;  // Keep

        case ITagVisitor::Action_Remove:
          return false;

        case ITagVisitor::Action_Replace:
          throw OrthancException(ErrorCode_NotImplemented, "Iterator cannot replace sequences");

        default:
          throw OrthancException(ErrorCode_ParameterOutOfRange);
      }

    }
  }


  void FromDcmtkBridge::Apply(DcmItem& dataset,
                              ITagVisitor& visitor,
                              Encoding defaultEncoding)
  {
    std::vector<DicomTag> parentTags;
    std::vector<size_t> parentIndexes;
    bool hasCodeExtensions;
    Encoding encoding = DetectEncoding(hasCodeExtensions, dataset, defaultEncoding);
    ApplyVisitorToDataset(dataset, visitor, parentTags, parentIndexes, encoding, hasCodeExtensions);
  }



  bool FromDcmtkBridge::LookupOrthancTransferSyntax(DicomTransferSyntax& target,
                                                    DcmFileFormat& dicom)
  {
    if (dicom.getDataset() == NULL)
    {
      throw OrthancException(ErrorCode_InternalError);
    }
    else
    {
      return LookupOrthancTransferSyntax(target, *dicom.getDataset());
    }
  }


  bool FromDcmtkBridge::LookupOrthancTransferSyntax(DicomTransferSyntax& target,
                                                    DcmDataset& dataset)
  {
    E_TransferSyntax xfer = dataset.getCurrentXfer();
    if (xfer == EXS_Unknown)
    {
      dataset.updateOriginalXfer();
      xfer = dataset.getOriginalXfer();
      if (xfer == EXS_Unknown)
      {
        throw OrthancException(ErrorCode_BadFileFormat,
                               "Cannot determine the transfer syntax of the DICOM instance");
      }
    }

    return FromDcmtkBridge::LookupOrthancTransferSyntax(target, xfer);
  }


  void FromDcmtkBridge::LogMissingTagsForStore(DcmDataset& dicom)
  {
    std::string patientId, studyInstanceUid, seriesInstanceUid, sopInstanceUid;

    const char* c = NULL;
    if (dicom.findAndGetString(DCM_PatientID, c).good() &&
        c != NULL)
    {
      patientId.assign(c);
    }

    c = NULL;
    if (dicom.findAndGetString(DCM_StudyInstanceUID, c).good() &&
        c != NULL)
    {
      studyInstanceUid.assign(c);
    }

    c = NULL;
    if (dicom.findAndGetString(DCM_SeriesInstanceUID, c).good() &&
        c != NULL)
    {
      seriesInstanceUid.assign(c);
    }

    c = NULL;
    if (dicom.findAndGetString(DCM_SOPInstanceUID, c).good() &&
        c != NULL)
    {
      sopInstanceUid.assign(c);
    }
    
    DicomMap::LogMissingTagsForStore(patientId, studyInstanceUid, seriesInstanceUid, sopInstanceUid);
  }


  void FromDcmtkBridge::IDicomPathVisitor::ApplyInternal(FromDcmtkBridge::IDicomPathVisitor& visitor,
                                                         DcmItem& item,
                                                         const DicomPath& pattern,
                                                         const DicomPath& actualPath)
  {
    const size_t level = actualPath.GetPrefixLength();
      
    if (level == pattern.GetPrefixLength())
    {
      visitor.Visit(item, actualPath);
    }
    else
    {
      assert(level < pattern.GetPrefixLength());

      const DicomTag& tmp = pattern.GetPrefixTag(level);
      DcmTagKey tag(tmp.GetGroup(), tmp.GetElement());

      DcmSequenceOfItems *sequence = NULL;
      if (item.findAndGetSequence(tag, sequence).good() &&
          sequence != NULL)
      {
        for (unsigned long i = 0; i < sequence->card(); i++)
        {
          if (pattern.IsPrefixUniversal(level) ||
              pattern.GetPrefixIndex(level) == static_cast<size_t>(i))
          {
            DcmItem *child = sequence->getItem(i);
            if (child != NULL)
            {
              DicomPath childPath = actualPath;
              childPath.AddIndexedTagToPrefix(pattern.GetPrefixTag(level), static_cast<size_t>(i));
              
              ApplyInternal(visitor, *child, pattern, childPath);
            }
          }
        }
      }
    }
  }


  void FromDcmtkBridge::IDicomPathVisitor::Apply(IDicomPathVisitor& visitor,
                                                 DcmDataset& dataset,
                                                 const DicomPath& path)
  {
    DicomPath actualPath(path.GetFinalTag());
    ApplyInternal(visitor, dataset, path, actualPath);
  }


  void FromDcmtkBridge::RemovePath(DcmDataset& dataset,
                                   const DicomPath& path)
  {
    class Visitor : public FromDcmtkBridge::IDicomPathVisitor
    {
    public:
      virtual void Visit(DcmItem& item,
                         const DicomPath& path) ORTHANC_OVERRIDE
      {
        DcmTagKey key(path.GetFinalTag().GetGroup(), path.GetFinalTag().GetElement());
        std::unique_ptr<DcmElement> removed(item.remove(key));
      }
    };
    
    Visitor visitor;
    IDicomPathVisitor::Apply(visitor, dataset, path);
  }
  

  void FromDcmtkBridge::ClearPath(DcmDataset& dataset,
                                  const DicomPath& path,
                                  bool onlyIfExists)
  {
    class Visitor : public FromDcmtkBridge::IDicomPathVisitor
    {
    public:
      bool  onlyIfExists_;
      
    public:
      explicit Visitor(bool onlyIfExists) :
        onlyIfExists_(onlyIfExists)
      {
      }
      
      virtual void Visit(DcmItem& item,
                         const DicomPath& path) ORTHANC_OVERRIDE
      {
        DcmTagKey key(path.GetFinalTag().GetGroup(), path.GetFinalTag().GetElement());

        if (onlyIfExists_ &&
            !item.tagExists(key))
        {
          // The tag is non-existing, do not clear it
        }
        else
        {
          if (!item.insertEmptyElement(key, OFTrue /* replace old value */).good())
          {
            throw OrthancException(ErrorCode_InternalError);
          }
        }
      }
    };
    
    Visitor visitor(onlyIfExists);
    IDicomPathVisitor::Apply(visitor, dataset, path);
  }
  

  void FromDcmtkBridge::ReplacePath(DcmDataset& dataset,
                                    const DicomPath& path,
                                    const DcmElement& element,
                                    DicomReplaceMode mode)
  {
    class Visitor : public FromDcmtkBridge::IDicomPathVisitor
    {
    private:
      std::unique_ptr<DcmElement> element_;
      DicomReplaceMode            mode_;
    
    public:
      Visitor(const DcmElement& element,
              DicomReplaceMode mode) :
        element_(dynamic_cast<DcmElement*>(element.clone())),
        mode_(mode)
      {
        if (element_.get() == NULL)
        {
          throw OrthancException(ErrorCode_InternalError, "Cannot clone DcmElement");
        }
      }
    
      virtual void Visit(DcmItem& item,
                         const DicomPath& path) ORTHANC_OVERRIDE
      {
        std::unique_ptr<DcmElement> cloned(dynamic_cast<DcmElement*>(element_->clone()));
        if (cloned.get() == NULL)
        {
          throw OrthancException(ErrorCode_InternalError, "Cannot clone DcmElement");
        }
        else
        {      
          DcmTagKey key(path.GetFinalTag().GetGroup(), path.GetFinalTag().GetElement());

          if (!item.tagExists(key))
          {
            switch (mode_)
            {
              case DicomReplaceMode_InsertIfAbsent:
                break;  // Fine, we can proceed with insertion
                
              case DicomReplaceMode_ThrowIfAbsent:
                throw OrthancException(ErrorCode_InexistentItem, "Cannot replace inexistent tag: " + GetTagName(*element_));
                
              case DicomReplaceMode_IgnoreIfAbsent:
                return;  // Don't proceed with insertion
                
              default:
                throw OrthancException(ErrorCode_ParameterOutOfRange);
            }
          }
          
          if (!item.insert(cloned.release(), OFTrue /* replace old */).good())
          {
            throw OrthancException(ErrorCode_InternalError, "Cannot replace an element: " + GetTagName(*element_));
          }
        }
      }
    };

    DcmTagKey key(path.GetFinalTag().GetGroup(), path.GetFinalTag().GetElement());
  
    if (element.getTag() != key)
    {
      throw OrthancException(ErrorCode_ParameterOutOfRange,
                             "The final tag must be the same as the tag of the element during a replacement");
    }
    else
    {
      Visitor visitor(element, mode);
      IDicomPathVisitor::Apply(visitor, dataset, path);
    }
  }


  bool FromDcmtkBridge::LookupSequenceItem(DicomMap& target,
                                           DcmDataset& dataset,
                                           const DicomPath& path,
                                           size_t sequenceIndex)
  {
    class Visitor : public FromDcmtkBridge::IDicomPathVisitor
    {
    private:
      bool       found_;
      DicomMap&  target_;
      size_t     sequenceIndex_;
      
    public:
      Visitor(DicomMap& target,
              size_t sequenceIndex) :
        found_(false),
        target_(target),
        sequenceIndex_(sequenceIndex)
      {
      }
      
      virtual void Visit(DcmItem& item,
                         const DicomPath& path) ORTHANC_OVERRIDE
      {
        DcmTagKey tag(path.GetFinalTag().GetGroup(), path.GetFinalTag().GetElement());

        DcmSequenceOfItems *sequence = NULL;
        
        if (item.findAndGetSequence(tag, sequence).good() &&
            sequence != NULL &&
            sequenceIndex_ < sequence->card())
        {
          std::set<DicomTag> ignoreTagLength;
          ExtractDicomSummary(target_, *sequence->getItem(sequenceIndex_), 0, ignoreTagLength);
          found_ = true;
        }
      }

      bool HasFound() const
      {
        return found_;
      }
    };

    Visitor visitor(target, sequenceIndex);
    IDicomPathVisitor::Apply(visitor, dataset, path);
    return visitor.HasFound();
  }


  bool FromDcmtkBridge::LookupStringValue(std::string& target,
                                          DcmDataset& dataset,
                                          const DicomTag& key)
  {
    DcmTagKey dcmkey(key.GetGroup(), key.GetElement());
    
    const char* str = NULL;
    const Uint8* data = NULL;
    unsigned long size = 0;

    if (dataset.findAndGetString(dcmkey, str).good() &&
        str != NULL)
    {
      target.assign(str);
      return true;
    }
    else if (dataset.findAndGetUint8Array(dcmkey, data, &size).good() &&
             data != NULL &&
             size > 0)
    {
      /**
       * This special case is necessary for borderline DICOM files
       * that have DICOM tags have the "UN" value representation. New
       * in Orthanc 1.10.1.
       * https://groups.google.com/g/orthanc-users/c/86fobx3ZyoM/m/KBG17un6AQAJ
       **/
      unsigned long l = 0;
      while (l < size &&
             data[l] != 0)
      {
        l++;
      }

      target.assign(reinterpret_cast<const char*>(data), l);
      return true;
    }
    else
    {
      return false;
    }
  }
}


#include "./FromDcmtkBridge_TransferSyntaxes.impl.h"