changeset 133:3251ec958a29

Option "RestrictTransferSyntaxes" saying which transfer syntaxes should be decoded with GDCM
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 09 Jun 2016 17:04:58 +0200
parents 2fffa4d0f313
children d73006baca3f
files CMakeLists.txt NEWS Orthanc/Core/DicomFormat/DicomMap.cpp Orthanc/Core/DicomFormat/DicomMap.h Orthanc/Core/DicomFormat/DicomTag.cpp Orthanc/Core/DicomFormat/DicomTag.h Orthanc/Core/DicomFormat/DicomValue.cpp Orthanc/Core/DicomFormat/DicomValue.h Orthanc/Core/Endianness.h Orthanc/Core/Enumerations.cpp Orthanc/Core/Enumerations.h Orthanc/Resources/CMake/DownloadPackage.cmake Plugin/Plugin.cpp Resources/SyncOrthancFolder.py
diffstat 14 files changed, 2199 insertions(+), 44 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Tue May 31 12:12:59 2016 +0200
+++ b/CMakeLists.txt	Thu Jun 09 17:04:58 2016 +0200
@@ -147,6 +147,9 @@
   ${CMAKE_SOURCE_DIR}/Orthanc/Core/SQLite/Transaction.cpp
   ${CMAKE_SOURCE_DIR}/Orthanc/Core/Toolbox.cpp
   ${CMAKE_SOURCE_DIR}/Orthanc/Core/Uuid.cpp
+  ${CMAKE_SOURCE_DIR}/Orthanc/Core/DicomFormat/DicomMap.cpp
+  ${CMAKE_SOURCE_DIR}/Orthanc/Core/DicomFormat/DicomTag.cpp
+  ${CMAKE_SOURCE_DIR}/Orthanc/Core/DicomFormat/DicomValue.cpp
   ${CMAKE_SOURCE_DIR}/Orthanc/Resources/ThirdParty/base64/base64.cpp
 
   ${CMAKE_SOURCE_DIR}/Plugin/Cache/CacheManager.cpp
--- a/NEWS	Tue May 31 12:12:59 2016 +0200
+++ b/NEWS	Thu Jun 09 17:04:58 2016 +0200
@@ -2,6 +2,7 @@
 ===============================
 
 * Option "EnableGdcm" to replace the built-in decoder of Orthanc with GDCM
+* Option "RestrictTransferSyntaxes" saying which transfer syntaxes should be decoded with GDCM
 * Fixed rendering of 16bpp images if values are < 0 or >= 32768
 * Decoding of JPEG2000 lossless images with YBR_RCT photometric interpretation
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/Core/DicomFormat/DicomMap.cpp	Thu Jun 09 17:04:58 2016 +0200
@@ -0,0 +1,783 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "DicomMap.h"
+
+#include <stdio.h>
+#include <memory>
+#include "../Endianness.h"
+#include "../OrthancException.h"
+
+
+namespace Orthanc
+{
+  static DicomTag patientTags[] =
+  {
+    //DicomTag(0x0010, 0x1010), // PatientAge
+    //DicomTag(0x0010, 0x1040)  // PatientAddress
+    DicomTag(0x0010, 0x0010),   // PatientName
+    DicomTag(0x0010, 0x0030),   // PatientBirthDate
+    DicomTag(0x0010, 0x0040),   // PatientSex
+    DicomTag(0x0010, 0x1000),   // OtherPatientIDs
+    DICOM_TAG_PATIENT_ID
+  };
+
+  static DicomTag studyTags[] =
+  {
+    //DicomTag(0x0010, 0x1020), // PatientSize
+    //DicomTag(0x0010, 0x1030)  // PatientWeight
+    DICOM_TAG_STUDY_DATE,
+    DicomTag(0x0008, 0x0030),   // StudyTime
+    DicomTag(0x0020, 0x0010),   // StudyID
+    DICOM_TAG_STUDY_DESCRIPTION,
+    DICOM_TAG_ACCESSION_NUMBER,
+    DICOM_TAG_STUDY_INSTANCE_UID,
+    DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION,   // New in db v6
+    DICOM_TAG_INSTITUTION_NAME,                  // New in db v6
+    DICOM_TAG_REQUESTING_PHYSICIAN,              // New in db v6
+    DICOM_TAG_REFERRING_PHYSICIAN_NAME           // New in db v6
+  };
+
+  static DicomTag seriesTags[] =
+  {
+    //DicomTag(0x0010, 0x1080), // MilitaryRank
+    DicomTag(0x0008, 0x0021),   // SeriesDate
+    DicomTag(0x0008, 0x0031),   // SeriesTime
+    DICOM_TAG_MODALITY,
+    DicomTag(0x0008, 0x0070),   // Manufacturer
+    DicomTag(0x0008, 0x1010),   // StationName
+    DICOM_TAG_SERIES_DESCRIPTION,
+    DicomTag(0x0018, 0x0015),   // BodyPartExamined
+    DicomTag(0x0018, 0x0024),   // SequenceName
+    DicomTag(0x0018, 0x1030),   // ProtocolName
+    DicomTag(0x0020, 0x0011),   // SeriesNumber
+    DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES,
+    DICOM_TAG_IMAGES_IN_ACQUISITION,
+    DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS,
+    DICOM_TAG_NUMBER_OF_SLICES,
+    DICOM_TAG_NUMBER_OF_TIME_SLICES,
+    DICOM_TAG_SERIES_INSTANCE_UID,
+    DICOM_TAG_IMAGE_ORIENTATION_PATIENT,                  // New in db v6
+    DICOM_TAG_SERIES_TYPE,                                // New in db v6
+    DICOM_TAG_OPERATOR_NAME,                              // New in db v6
+    DICOM_TAG_PERFORMED_PROCEDURE_STEP_DESCRIPTION,       // New in db v6
+    DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION,  // New in db v6
+    DICOM_TAG_CONTRAST_BOLUS_AGENT                        // New in db v6
+  };
+
+  static DicomTag instanceTags[] =
+  {
+    DicomTag(0x0008, 0x0012),   // InstanceCreationDate
+    DicomTag(0x0008, 0x0013),   // InstanceCreationTime
+    DicomTag(0x0020, 0x0012),   // AcquisitionNumber
+    DICOM_TAG_IMAGE_INDEX,
+    DICOM_TAG_INSTANCE_NUMBER,
+    DICOM_TAG_NUMBER_OF_FRAMES,
+    DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER,
+    DICOM_TAG_SOP_INSTANCE_UID,
+    DICOM_TAG_IMAGE_POSITION_PATIENT,    // New in db v6
+    DICOM_TAG_IMAGE_COMMENTS             // New in db v6
+  };
+
+
+  void DicomMap::LoadMainDicomTags(const DicomTag*& tags,
+                                   size_t& size,
+                                   ResourceType level)
+  {
+    switch (level)
+    {
+      case ResourceType_Patient:
+        tags = patientTags;
+        size = sizeof(patientTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Study:
+        tags = studyTags;
+        size = sizeof(studyTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Series:
+        tags = seriesTags;
+        size = sizeof(seriesTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Instance:
+        tags = instanceTags;
+        size = sizeof(instanceTags) / sizeof(DicomTag);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  void DicomMap::SetValue(uint16_t group, 
+                          uint16_t element, 
+                          DicomValue* value)
+  {
+    DicomTag tag(group, element);
+    Map::iterator it = map_.find(tag);
+
+    if (it != map_.end())
+    {
+      delete it->second;
+      it->second = value;
+    }
+    else
+    {
+      map_.insert(std::make_pair(tag, value));
+    }
+  }
+
+  void DicomMap::SetValue(DicomTag tag, 
+                          DicomValue* value)
+  {
+    SetValue(tag.GetGroup(), tag.GetElement(), value);
+  }
+
+
+
+
+  void DicomMap::Clear()
+  {
+    for (Map::iterator it = map_.begin(); it != map_.end(); ++it)
+    {
+      delete it->second;
+    }
+
+    map_.clear();
+  }
+
+
+  void DicomMap::ExtractTags(DicomMap& result,
+                             const DicomTag* tags,
+                             size_t count) const
+  {
+    result.Clear();
+
+    for (unsigned int i = 0; i < count; i++)
+    {
+      Map::const_iterator it = map_.find(tags[i]);
+      if (it != map_.end())
+      {
+        result.SetValue(it->first, it->second->Clone());
+      }
+    }
+  }
+
+
+  void DicomMap::ExtractPatientInformation(DicomMap& result) const
+  {
+    ExtractTags(result, patientTags, sizeof(patientTags) / sizeof(DicomTag));
+  }
+
+  void DicomMap::ExtractStudyInformation(DicomMap& result) const
+  {
+    ExtractTags(result, studyTags, sizeof(studyTags) / sizeof(DicomTag));
+  }
+
+  void DicomMap::ExtractSeriesInformation(DicomMap& result) const
+  {
+    ExtractTags(result, seriesTags, sizeof(seriesTags) / sizeof(DicomTag));
+  }
+
+  void DicomMap::ExtractInstanceInformation(DicomMap& result) const
+  {
+    ExtractTags(result, instanceTags, sizeof(instanceTags) / sizeof(DicomTag));
+  }
+
+
+
+  DicomMap* DicomMap::Clone() const
+  {
+    std::auto_ptr<DicomMap> result(new DicomMap);
+
+    for (Map::const_iterator it = map_.begin(); it != map_.end(); ++it)
+    {
+      result->map_.insert(std::make_pair(it->first, it->second->Clone()));
+    }
+
+    return result.release();
+  }
+
+
+  void DicomMap::Assign(const DicomMap& other)
+  {
+    Clear();
+
+    for (Map::const_iterator it = other.map_.begin(); it != other.map_.end(); ++it)
+    {
+      map_.insert(std::make_pair(it->first, it->second->Clone()));
+    }
+  }
+
+
+  const DicomValue& DicomMap::GetValue(const DicomTag& tag) const
+  {
+    const DicomValue* value = TestAndGetValue(tag);
+
+    if (value)
+    {
+      return *value;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_InexistentTag);
+    }
+  }
+
+
+  const DicomValue* DicomMap::TestAndGetValue(const DicomTag& tag) const
+  {
+    Map::const_iterator it = map_.find(tag);
+
+    if (it == map_.end())
+    {
+      return NULL;
+    }
+    else
+    {
+      return it->second;
+    }
+  }
+
+
+  void DicomMap::Remove(const DicomTag& tag) 
+  {
+    Map::iterator it = map_.find(tag);
+    if (it != map_.end())
+    {
+      delete it->second;
+      map_.erase(it);
+    }
+  }
+
+
+  static void SetupFindTemplate(DicomMap& result,
+                                const DicomTag* tags,
+                                size_t count) 
+  {
+    result.Clear();
+
+    for (size_t i = 0; i < count; i++)
+    {
+      result.SetValue(tags[i], "", false);
+    }
+  }
+
+  void DicomMap::SetupFindPatientTemplate(DicomMap& result)
+  {
+    SetupFindTemplate(result, patientTags, sizeof(patientTags) / sizeof(DicomTag));
+  }
+
+  void DicomMap::SetupFindStudyTemplate(DicomMap& result)
+  {
+    SetupFindTemplate(result, studyTags, sizeof(studyTags) / sizeof(DicomTag));
+    result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false);
+    result.SetValue(DICOM_TAG_PATIENT_ID, "", false);
+
+    // These main DICOM tags are only indirectly related to the
+    // General Study Module, remove them
+    result.Remove(DICOM_TAG_INSTITUTION_NAME);
+    result.Remove(DICOM_TAG_REQUESTING_PHYSICIAN);
+    result.Remove(DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION);
+  }
+
+  void DicomMap::SetupFindSeriesTemplate(DicomMap& result)
+  {
+    SetupFindTemplate(result, seriesTags, sizeof(seriesTags) / sizeof(DicomTag));
+    result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false);
+    result.SetValue(DICOM_TAG_PATIENT_ID, "", false);
+    result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false);
+
+    // These tags are considered as "main" by Orthanc, but are not in the Series module
+    result.Remove(DicomTag(0x0008, 0x0070));  // Manufacturer
+    result.Remove(DicomTag(0x0008, 0x1010));  // Station name
+    result.Remove(DicomTag(0x0018, 0x0024));  // Sequence name
+    result.Remove(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES);
+    result.Remove(DICOM_TAG_IMAGES_IN_ACQUISITION);
+    result.Remove(DICOM_TAG_NUMBER_OF_SLICES);
+    result.Remove(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS);
+    result.Remove(DICOM_TAG_NUMBER_OF_TIME_SLICES);
+    result.Remove(DICOM_TAG_IMAGE_ORIENTATION_PATIENT);
+    result.Remove(DICOM_TAG_SERIES_TYPE);
+    result.Remove(DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION);
+    result.Remove(DICOM_TAG_CONTRAST_BOLUS_AGENT);
+  }
+
+  void DicomMap::SetupFindInstanceTemplate(DicomMap& result)
+  {
+    SetupFindTemplate(result, instanceTags, sizeof(instanceTags) / sizeof(DicomTag));
+    result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false);
+    result.SetValue(DICOM_TAG_PATIENT_ID, "", false);
+    result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false);
+    result.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "", false);
+  }
+
+
+  void DicomMap::CopyTagIfExists(const DicomMap& source,
+                                 const DicomTag& tag)
+  {
+    if (source.HasTag(tag))
+    {
+      SetValue(tag, source.GetValue(tag));
+    }
+  }
+
+
+  bool DicomMap::IsMainDicomTag(const DicomTag& tag, ResourceType level)
+  {
+    DicomTag *tags = NULL;
+    size_t size;
+
+    switch (level)
+    {
+      case ResourceType_Patient:
+        tags = patientTags;
+        size = sizeof(patientTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Study:
+        tags = studyTags;
+        size = sizeof(studyTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Series:
+        tags = seriesTags;
+        size = sizeof(seriesTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Instance:
+        tags = instanceTags;
+        size = sizeof(instanceTags) / sizeof(DicomTag);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    for (size_t i = 0; i < size; i++)
+    {
+      if (tags[i] == tag)
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  bool DicomMap::IsMainDicomTag(const DicomTag& tag)
+  {
+    return (IsMainDicomTag(tag, ResourceType_Patient) ||
+            IsMainDicomTag(tag, ResourceType_Study) ||
+            IsMainDicomTag(tag, ResourceType_Series) ||
+            IsMainDicomTag(tag, ResourceType_Instance));
+  }
+
+
+  void DicomMap::GetMainDicomTagsInternal(std::set<DicomTag>& result, ResourceType level)
+  {
+    DicomTag *tags = NULL;
+    size_t size;
+
+    switch (level)
+    {
+      case ResourceType_Patient:
+        tags = patientTags;
+        size = sizeof(patientTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Study:
+        tags = studyTags;
+        size = sizeof(studyTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Series:
+        tags = seriesTags;
+        size = sizeof(seriesTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Instance:
+        tags = instanceTags;
+        size = sizeof(instanceTags) / sizeof(DicomTag);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    for (size_t i = 0; i < size; i++)
+    {
+      result.insert(tags[i]);
+    }
+  }
+
+
+  void DicomMap::GetMainDicomTags(std::set<DicomTag>& result, ResourceType level)
+  {
+    result.clear();
+    GetMainDicomTagsInternal(result, level);
+  }
+
+
+  void DicomMap::GetMainDicomTags(std::set<DicomTag>& result)
+  {
+    result.clear();
+    GetMainDicomTagsInternal(result, ResourceType_Patient);
+    GetMainDicomTagsInternal(result, ResourceType_Study);
+    GetMainDicomTagsInternal(result, ResourceType_Series);
+    GetMainDicomTagsInternal(result, ResourceType_Instance);
+  }
+
+
+  void DicomMap::GetTags(std::set<DicomTag>& tags) const
+  {
+    tags.clear();
+
+    for (Map::const_iterator it = map_.begin();
+         it != map_.end(); ++it)
+    {
+      tags.insert(it->first);
+    }
+  }
+
+
+  static uint16_t ReadUnsignedInteger16(const char* dicom)
+  {
+    return le16toh(*reinterpret_cast<const uint16_t*>(dicom));
+  }
+
+
+  static uint32_t ReadUnsignedInteger32(const char* dicom)
+  {
+    return le32toh(*reinterpret_cast<const uint32_t*>(dicom));
+  }
+
+
+  static bool ValidateTag(const ValueRepresentation& vr,
+                          const std::string& value)
+  {
+    switch (vr)
+    {
+      case ValueRepresentation_ApplicationEntity:
+        return value.size() <= 16;
+
+      case ValueRepresentation_AgeString:
+        return (value.size() == 4 &&
+                isdigit(value[0]) &&
+                isdigit(value[1]) &&
+                isdigit(value[2]) &&
+                (value[3] == 'D' || value[3] == 'W' || value[3] == 'M' || value[3] == 'Y'));
+
+      case ValueRepresentation_AttributeTag:
+        return value.size() == 4;
+
+      case ValueRepresentation_CodeString:
+        return value.size() <= 16;
+
+      case ValueRepresentation_Date:
+        return value.size() <= 18;
+
+      case ValueRepresentation_DecimalString:
+        return value.size() <= 16;
+
+      case ValueRepresentation_DateTime:
+        return value.size() <= 54;
+
+      case ValueRepresentation_FloatingPointSingle:
+        return value.size() == 4;
+
+      case ValueRepresentation_FloatingPointDouble:
+        return value.size() == 8;
+
+      case ValueRepresentation_IntegerString:
+        return value.size() <= 12;
+
+      case ValueRepresentation_LongString:
+        return value.size() <= 64;
+
+      case ValueRepresentation_LongText:
+        return value.size() <= 10240;
+
+      case ValueRepresentation_OtherByte:
+        return true;
+      
+      case ValueRepresentation_OtherDouble:
+        return value.size() <= static_cast<size_t>((1llu << 32) - 8);
+
+      case ValueRepresentation_OtherFloat:
+        return value.size() <= static_cast<size_t>((1llu << 32) - 4);
+
+      case ValueRepresentation_OtherLong:
+        return true;
+
+      case ValueRepresentation_OtherWord:
+        return true;
+
+      case ValueRepresentation_PersonName:
+        return true;
+
+      case ValueRepresentation_ShortString:
+        return value.size() <= 16;
+
+      case ValueRepresentation_SignedLong:
+        return value.size() == 4;
+
+      case ValueRepresentation_Sequence:
+        return true;
+
+      case ValueRepresentation_SignedShort:
+        return value.size() == 2;
+
+      case ValueRepresentation_ShortText:
+        return value.size() <= 1024;
+
+      case ValueRepresentation_Time:
+        return value.size() <= 28;
+
+      case ValueRepresentation_UnlimitedCharacters:
+        return value.size() <= static_cast<size_t>((1llu << 32) - 2);
+
+      case ValueRepresentation_UniqueIdentifier:
+        return value.size() <= 64;
+
+      case ValueRepresentation_UnsignedLong:
+        return value.size() == 4;
+
+      case ValueRepresentation_Unknown:
+        return true;
+
+      case ValueRepresentation_UniversalResource:
+        return value.size() <= static_cast<size_t>((1llu << 32) - 2);
+
+      case ValueRepresentation_UnsignedShort:
+        return value.size() == 2;
+
+      case ValueRepresentation_UnlimitedText:
+        return value.size() <= static_cast<size_t>((1llu << 32) - 2);
+
+      default:
+        // Assume unsupported tags are OK
+        return true;
+    }
+  }
+
+
+  static void RemoveTagPadding(std::string& value,
+                               const ValueRepresentation& vr)
+  {
+    /**
+     * Remove padding from character strings, if need be. For the time
+     * being, only the UI VR is supported.
+     * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html
+     **/
+
+    switch (vr)
+    {
+      case ValueRepresentation_UniqueIdentifier:
+      {
+        /**
+         * "Values with a VR of UI shall be padded with a single
+         * trailing NULL (00H) character when necessary to achieve even
+         * length."
+         **/
+
+        if (!value.empty() &&
+            value[value.size() - 1] == '\0')
+        {
+          value.resize(value.size() - 1);
+        }
+
+        break;
+      }
+
+      /**
+       * TODO implement other VR
+       **/
+
+      default:
+        // No padding is applicable to this VR
+        break;
+    }
+  }
+
+
+  static bool ReadNextTag(DicomTag& tag,
+                          ValueRepresentation& vr,
+                          std::string& value,
+                          const char* dicom,
+                          size_t size,
+                          size_t& position)
+  {
+    /**
+     * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/chapter_7.html#sect_7.1.2
+     * This function reads a data element with Explicit VR encoded using Little-Endian.
+     **/
+
+    if (position + 6 > size)
+    {
+      return false;
+    }
+
+    tag = DicomTag(ReadUnsignedInteger16(dicom + position),
+                   ReadUnsignedInteger16(dicom + position + 2));
+
+    vr = StringToValueRepresentation(std::string(dicom + position + 4, 2), true);
+    if (vr == ValueRepresentation_NotSupported)
+    {
+      return false;
+    }
+
+    if (vr == ValueRepresentation_OtherByte ||
+        vr == ValueRepresentation_OtherDouble ||
+        vr == ValueRepresentation_OtherFloat ||
+        vr == ValueRepresentation_OtherLong ||
+        vr == ValueRepresentation_OtherWord ||
+        vr == ValueRepresentation_Sequence ||
+        vr == ValueRepresentation_UnlimitedCharacters ||
+        vr == ValueRepresentation_UniversalResource ||
+        vr == ValueRepresentation_UnlimitedText ||
+        vr == ValueRepresentation_Unknown)    // Note that "UN" should never appear in the Meta Information
+    {
+      if (position + 12 > size)
+      {
+        return false;
+      }
+
+      uint32_t length = ReadUnsignedInteger32(dicom + position + 8);
+
+      if (position + 12 + length > size)
+      {
+        return false;
+      }
+
+      value.assign(dicom + position + 12, length);
+      position += (12 + length);
+    }
+    else
+    {
+      if (position + 8 > size)
+      {
+        return false;
+      }
+
+      uint16_t length = ReadUnsignedInteger16(dicom + position + 6);
+
+      if (position + 8 + length > size)
+      {
+        return false;
+      }
+
+      value.assign(dicom + position + 8, length);
+      position += (8 + length);
+    }
+
+    if (!ValidateTag(vr, value))
+    {
+      return false;
+    }
+
+    RemoveTagPadding(value, vr);
+
+    return true;
+  }
+
+
+  bool DicomMap::ParseDicomMetaInformation(DicomMap& result,
+                                           const char* dicom,
+                                           size_t size)
+  {
+    /**
+     * http://dicom.nema.org/medical/dicom/current/output/chtml/part10/chapter_7.html
+     * According to Table 7.1-1, besides the "DICM" DICOM prefix, the
+     * file preamble (i.e. dicom[0..127]) should not be taken into
+     * account to determine whether the file is or is not a DICOM file.
+     **/
+
+    if (size < 132 ||
+        dicom[128] != 'D' ||
+        dicom[129] != 'I' ||
+        dicom[130] != 'C' ||
+        dicom[131] != 'M')
+    {
+      return false;
+    }
+
+
+    /**
+     * The DICOM File Meta Information must be encoded using the
+     * Explicit VR Little Endian Transfer Syntax
+     * (UID=1.2.840.10008.1.2.1).
+     **/
+
+    result.Clear();
+
+    // First, we read the "File Meta Information Group Length" tag
+    // (0002,0000) to know where to stop reading the meta header
+    size_t position = 132;
+
+    DicomTag tag(0x0000, 0x0000);  // Dummy initialization
+    ValueRepresentation vr;
+    std::string value;
+    if (!ReadNextTag(tag, vr, value, dicom, size, position) ||
+        tag.GetGroup() != 0x0002 ||
+        tag.GetElement() != 0x0000 ||
+        vr != ValueRepresentation_UnsignedLong ||
+        value.size() != 4)
+    {
+      return false;
+    }
+
+    size_t stopPosition = position + ReadUnsignedInteger32(value.c_str());
+    if (stopPosition > size)
+    {
+      return false;
+    }
+
+    while (position < stopPosition)
+    {
+      if (ReadNextTag(tag, vr, value, dicom, size, position))
+      {
+        result.SetValue(tag, value, IsBinaryValueRepresentation(vr));
+      }
+      else
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/Core/DicomFormat/DicomMap.h	Thu Jun 09 17:04:58 2016 +0200
@@ -0,0 +1,186 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "DicomTag.h"
+#include "DicomValue.h"
+#include "../Enumerations.h"
+
+#include <set>
+#include <map>
+#include <json/json.h>
+
+namespace Orthanc
+{
+  class DicomMap : public boost::noncopyable
+  {
+  private:
+    friend class DicomArray;
+    friend class FromDcmtkBridge;
+    friend class ToDcmtkBridge;
+
+    typedef std::map<DicomTag, DicomValue*>  Map;
+
+    Map map_;
+
+    // Warning: This takes the ownership of "value"
+    void SetValue(uint16_t group, 
+                  uint16_t element, 
+                  DicomValue* value);
+
+    void SetValue(DicomTag tag, 
+                  DicomValue* value);
+
+    void ExtractTags(DicomMap& source,
+                     const DicomTag* tags,
+                     size_t count) const;
+   
+    static void GetMainDicomTagsInternal(std::set<DicomTag>& result, ResourceType level);
+
+  public:
+    DicomMap()
+    {
+    }
+
+    ~DicomMap()
+    {
+      Clear();
+    }
+
+    size_t GetSize() const
+    {
+      return map_.size();
+    }
+    
+    DicomMap* Clone() const;
+
+    void Assign(const DicomMap& other);
+
+    void Clear();
+
+    void SetValue(uint16_t group, 
+                  uint16_t element, 
+                  const DicomValue& value)
+    {
+      SetValue(group, element, value.Clone());
+    }
+
+    void SetValue(const DicomTag& tag,
+                  const DicomValue& value)
+    {
+      SetValue(tag, value.Clone());
+    }
+
+    void SetValue(const DicomTag& tag,
+                  const std::string& str,
+                  bool isBinary)
+    {
+      SetValue(tag, new DicomValue(str, isBinary));
+    }
+
+    void SetValue(uint16_t group, 
+                  uint16_t element, 
+                  const std::string& str,
+                  bool isBinary)
+    {
+      SetValue(group, element, new DicomValue(str, isBinary));
+    }
+
+    bool HasTag(uint16_t group, uint16_t element) const
+    {
+      return HasTag(DicomTag(group, element));
+    }
+
+    bool HasTag(const DicomTag& tag) const
+    {
+      return map_.find(tag) != map_.end();
+    }
+
+    const DicomValue& GetValue(uint16_t group, uint16_t element) const
+    {
+      return GetValue(DicomTag(group, element));
+    }
+
+    const DicomValue& GetValue(const DicomTag& tag) const;
+
+    // DO NOT delete the returned value!
+    const DicomValue* TestAndGetValue(uint16_t group, uint16_t element) const
+    {
+      return TestAndGetValue(DicomTag(group, element));
+    }       
+
+    // DO NOT delete the returned value!
+    const DicomValue* TestAndGetValue(const DicomTag& tag) const;
+
+    void Remove(const DicomTag& tag);
+
+    void ExtractPatientInformation(DicomMap& result) const;
+
+    void ExtractStudyInformation(DicomMap& result) const;
+
+    void ExtractSeriesInformation(DicomMap& result) const;
+
+    void ExtractInstanceInformation(DicomMap& result) const;
+
+    static void SetupFindPatientTemplate(DicomMap& result);
+
+    static void SetupFindStudyTemplate(DicomMap& result);
+
+    static void SetupFindSeriesTemplate(DicomMap& result);
+
+    static void SetupFindInstanceTemplate(DicomMap& result);
+
+    void CopyTagIfExists(const DicomMap& source,
+                         const DicomTag& tag);
+
+    static bool IsMainDicomTag(const DicomTag& tag, ResourceType level);
+
+    static bool IsMainDicomTag(const DicomTag& tag);
+
+    static void GetMainDicomTags(std::set<DicomTag>& result, ResourceType level);
+
+    static void GetMainDicomTags(std::set<DicomTag>& result);
+
+    void Print(FILE* fp) const;
+
+    void GetTags(std::set<DicomTag>& tags) const;
+
+    static void LoadMainDicomTags(const DicomTag*& tags,
+                                  size_t& size,
+                                  ResourceType level);
+
+    static bool ParseDicomMetaInformation(DicomMap& result,
+                                          const char* dicom,
+                                          size_t size);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/Core/DicomFormat/DicomTag.cpp	Thu Jun 09 17:04:58 2016 +0200
@@ -0,0 +1,252 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "DicomTag.h"
+
+#include "../OrthancException.h"
+
+#include <iostream>
+#include <iomanip>
+#include <stdio.h>
+
+namespace Orthanc
+{
+  bool DicomTag::operator< (const DicomTag& other) const
+  {
+    if (group_ < other.group_)
+      return true;
+
+    if (group_ > other.group_)
+      return false;
+
+    return element_ < other.element_;
+  }
+
+
+  std::ostream& operator<< (std::ostream& o, const DicomTag& tag)
+  {
+    using namespace std;
+    ios_base::fmtflags state = o.flags();
+    o.flags(ios::right | ios::hex);
+    o << "(" << setfill('0') << setw(4) << tag.GetGroup()
+      << "," << setw(4) << tag.GetElement() << ")";
+    o.flags(state);
+    return o;
+  }
+
+
+  std::string DicomTag::Format() const
+  {
+    char b[16];
+    sprintf(b, "%04x,%04x", group_, element_);
+    return std::string(b);
+  }
+
+
+  const char* DicomTag::GetMainTagsName() const
+  {
+    if (*this == DICOM_TAG_ACCESSION_NUMBER)
+      return "AccessionNumber";
+
+    if (*this == DICOM_TAG_SOP_INSTANCE_UID)
+      return "SOPInstanceUID";
+
+    if (*this == DICOM_TAG_PATIENT_ID)
+      return "PatientID";
+
+    if (*this == DICOM_TAG_SERIES_INSTANCE_UID)
+      return "SeriesInstanceUID";
+
+    if (*this == DICOM_TAG_STUDY_INSTANCE_UID)
+      return "StudyInstanceUID"; 
+
+    if (*this == DICOM_TAG_PIXEL_DATA)
+      return "PixelData";
+
+    if (*this == DICOM_TAG_IMAGE_INDEX)
+      return "ImageIndex";
+
+    if (*this == DICOM_TAG_INSTANCE_NUMBER)
+      return "InstanceNumber";
+
+    if (*this == DICOM_TAG_NUMBER_OF_SLICES)
+      return "NumberOfSlices";
+
+    if (*this == DICOM_TAG_NUMBER_OF_FRAMES)
+      return "NumberOfFrames";
+
+    if (*this == DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES)
+      return "CardiacNumberOfImages";
+
+    if (*this == DICOM_TAG_IMAGES_IN_ACQUISITION)
+      return "ImagesInAcquisition";
+
+    if (*this == DICOM_TAG_PATIENT_NAME)
+      return "PatientName";
+
+    if (*this == DICOM_TAG_IMAGE_POSITION_PATIENT)
+      return "ImagePositionPatient";
+
+    if (*this == DICOM_TAG_IMAGE_ORIENTATION_PATIENT)
+      return "ImageOrientationPatient";
+
+    return "";
+  }
+
+
+  void DicomTag::AddTagsForModule(std::set<DicomTag>& target,
+                                  DicomModule module)
+  {
+    // REFERENCE: 11_03pu.pdf, DICOM PS 3.3 2011 - Information Object Definitions
+
+    switch (module)
+    {
+      case DicomModule_Patient:
+        // This is Table C.7-1 "Patient Module Attributes" (p. 373)
+        target.insert(DicomTag(0x0010, 0x0010));   // Patient's name
+        target.insert(DicomTag(0x0010, 0x0020));   // Patient ID
+        target.insert(DicomTag(0x0010, 0x0030));   // Patient's birth date
+        target.insert(DicomTag(0x0010, 0x0040));   // Patient's sex
+        target.insert(DicomTag(0x0008, 0x1120));   // Referenced patient sequence
+        target.insert(DicomTag(0x0010, 0x0032));   // Patient's birth time
+        target.insert(DicomTag(0x0010, 0x1000));   // Other patient IDs
+        target.insert(DicomTag(0x0010, 0x1002));   // Other patient IDs sequence
+        target.insert(DicomTag(0x0010, 0x1001));   // Other patient names
+        target.insert(DicomTag(0x0010, 0x2160));   // Ethnic group
+        target.insert(DicomTag(0x0010, 0x4000));   // Patient comments
+        target.insert(DicomTag(0x0010, 0x2201));   // Patient species description
+        target.insert(DicomTag(0x0010, 0x2202));   // Patient species code sequence
+        target.insert(DicomTag(0x0010, 0x2292));   // Patient breed description
+        target.insert(DicomTag(0x0010, 0x2293));   // Patient breed code sequence
+        target.insert(DicomTag(0x0010, 0x2294));   // Breed registration sequence
+        target.insert(DicomTag(0x0010, 0x2297));   // Responsible person
+        target.insert(DicomTag(0x0010, 0x2298));   // Responsible person role
+        target.insert(DicomTag(0x0010, 0x2299));   // Responsible organization
+        target.insert(DicomTag(0x0012, 0x0062));   // Patient identity removed
+        target.insert(DicomTag(0x0012, 0x0063));   // De-identification method
+        target.insert(DicomTag(0x0012, 0x0064));   // De-identification method code sequence
+
+        // Table 10-18 ISSUER OF PATIENT ID MACRO (p. 112)
+        target.insert(DicomTag(0x0010, 0x0021));   // Issuer of Patient ID
+        target.insert(DicomTag(0x0010, 0x0024));   // Issuer of Patient ID qualifiers sequence
+        break;
+
+      case DicomModule_Study:
+        // This is Table C.7-3 "General Study Module Attributes" (p. 378)
+        target.insert(DicomTag(0x0020, 0x000d));   // Study instance UID
+        target.insert(DicomTag(0x0008, 0x0020));   // Study date
+        target.insert(DicomTag(0x0008, 0x0030));   // Study time
+        target.insert(DicomTag(0x0008, 0x0090));   // Referring physician's name
+        target.insert(DicomTag(0x0008, 0x0096));   // Referring physician identification sequence
+        target.insert(DicomTag(0x0020, 0x0010));   // Study ID
+        target.insert(DicomTag(0x0008, 0x0050));   // Accession number
+        target.insert(DicomTag(0x0008, 0x0051));   // Issuer of accession number sequence
+        target.insert(DicomTag(0x0008, 0x1030));   // Study description
+        target.insert(DicomTag(0x0008, 0x1048));   // Physician(s) of record
+        target.insert(DicomTag(0x0008, 0x1049));   // Physician(s) of record identification sequence
+        target.insert(DicomTag(0x0008, 0x1060));   // Name of physician(s) reading study
+        target.insert(DicomTag(0x0008, 0x1062));   // Physician(s) reading study identification sequence
+        target.insert(DicomTag(0x0032, 0x1034));   // Requesting service code sequence
+        target.insert(DicomTag(0x0008, 0x1110));   // Referenced study sequence
+        target.insert(DicomTag(0x0008, 0x1032));   // Procedure code sequence
+        target.insert(DicomTag(0x0040, 0x1012));   // Reason for performed procedure code sequence
+        break;
+
+      case DicomModule_Series:
+        // This is Table C.7-5 "General Series Module Attributes" (p. 385)
+        target.insert(DicomTag(0x0008, 0x0060));   // Modality 
+        target.insert(DicomTag(0x0020, 0x000e));   // Series Instance UID 
+        target.insert(DicomTag(0x0020, 0x0011));   // Series Number 
+        target.insert(DicomTag(0x0020, 0x0060));   // Laterality 
+        target.insert(DicomTag(0x0008, 0x0021));   // Series Date 
+        target.insert(DicomTag(0x0008, 0x0031));   // Series Time 
+        target.insert(DicomTag(0x0008, 0x1050));   // Performing Physicians’ Name 
+        target.insert(DicomTag(0x0008, 0x1052));   // Performing Physician Identification Sequence 
+        target.insert(DicomTag(0x0018, 0x1030));   // Protocol Name
+        target.insert(DicomTag(0x0008, 0x103e));   // Series Description 
+        target.insert(DicomTag(0x0008, 0x103f));   // Series Description Code Sequence 
+        target.insert(DicomTag(0x0008, 0x1070));   // Operators' Name 
+        target.insert(DicomTag(0x0008, 0x1072));   // Operator Identification Sequence 
+        target.insert(DicomTag(0x0008, 0x1111));   // Referenced Performed Procedure Step Sequence
+        target.insert(DicomTag(0x0008, 0x1250));   // Related Series Sequence
+        target.insert(DicomTag(0x0018, 0x0015));   // Body Part Examined
+        target.insert(DicomTag(0x0018, 0x5100));   // Patient Position
+        target.insert(DicomTag(0x0028, 0x0108));   // Smallest Pixel Value in Series 
+        target.insert(DicomTag(0x0029, 0x0109));   // Largest Pixel Value in Series 
+        target.insert(DicomTag(0x0040, 0x0275));   // Request Attributes Sequence 
+        target.insert(DicomTag(0x0010, 0x2210));   // Anatomical Orientation Type
+
+        // Table 10-16 PERFORMED PROCEDURE STEP SUMMARY MACRO ATTRIBUTES
+        target.insert(DicomTag(0x0040, 0x0253));   // Performed Procedure Step ID 
+        target.insert(DicomTag(0x0040, 0x0244));   // Performed Procedure Step Start Date 
+        target.insert(DicomTag(0x0040, 0x0245));   // Performed Procedure Step Start Time 
+        target.insert(DicomTag(0x0040, 0x0254));   // Performed Procedure Step Description 
+        target.insert(DicomTag(0x0040, 0x0260));   // Performed Protocol Code Sequence 
+        target.insert(DicomTag(0x0040, 0x0280));   // Comments on the Performed Procedure Step
+        break;
+
+      case DicomModule_Instance:
+        // This is Table C.12-1 "SOP Common Module Attributes" (p. 1207)
+        target.insert(DicomTag(0x0008, 0x0016));   // SOP Class UID
+        target.insert(DicomTag(0x0008, 0x0018));   // SOP Instance UID 
+        target.insert(DicomTag(0x0008, 0x0005));   // Specific Character Set 
+        target.insert(DicomTag(0x0008, 0x0012));   // Instance Creation Date 
+        target.insert(DicomTag(0x0008, 0x0013));   // Instance Creation Time 
+        target.insert(DicomTag(0x0008, 0x0014));   // Instance Creator UID 
+        target.insert(DicomTag(0x0008, 0x001a));   // Related General SOP Class UID 
+        target.insert(DicomTag(0x0008, 0x001b));   // Original Specialized SOP Class UID 
+        target.insert(DicomTag(0x0008, 0x0110));   // Coding Scheme Identification Sequence 
+        target.insert(DicomTag(0x0008, 0x0201));   // Timezone Offset From UTC 
+        target.insert(DicomTag(0x0018, 0xa001));   // Contributing Equipment Sequence
+        target.insert(DicomTag(0x0020, 0x0013));   // Instance Number 
+        target.insert(DicomTag(0x0100, 0x0410));   // SOP Instance Status 
+        target.insert(DicomTag(0x0100, 0x0420));   // SOP Authorization DateTime 
+        target.insert(DicomTag(0x0100, 0x0424));   // SOP Authorization Comment 
+        target.insert(DicomTag(0x0100, 0x0426));   // Authorization Equipment Certification Number
+        target.insert(DicomTag(0x0400, 0x0500));   // Encrypted Attributes Sequence
+        target.insert(DicomTag(0x0400, 0x0561));   // Original Attributes Sequence 
+        target.insert(DicomTag(0x0040, 0xa390));   // HL7 Structured Document Reference Sequence
+        target.insert(DicomTag(0x0028, 0x0303));   // Longitudinal Temporal Information Modified 
+
+        // Table C.12-6 "DIGITAL SIGNATURES MACRO ATTRIBUTES" (p. 1216)
+        target.insert(DicomTag(0x4ffe, 0x0001));   // MAC Parameters sequence
+        target.insert(DicomTag(0xfffa, 0xfffa));   // Digital signatures sequence
+        break;
+
+        // TODO IMAGE MODULE?
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/Core/DicomFormat/DicomTag.h	Thu Jun 09 17:04:58 2016 +0200
@@ -0,0 +1,182 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <string>
+#include <set>
+#include <stdint.h>
+
+#include "../Enumerations.h"
+
+namespace Orthanc
+{
+  class DicomTag
+  {
+    // This must stay a POD (plain old data structure) 
+
+  private:
+    uint16_t group_;
+    uint16_t element_;
+
+  public:
+    DicomTag(uint16_t group,
+             uint16_t element) :
+      group_(group),
+      element_(element)
+    {
+    }
+
+    uint16_t GetGroup() const
+    {
+      return group_;
+    }
+
+    uint16_t GetElement() const
+    {
+      return element_;
+    }
+
+    bool IsPrivate() const
+    {
+      return group_ % 2 == 1;
+    }
+
+    const char* GetMainTagsName() const;
+
+    bool operator< (const DicomTag& other) const;
+
+    bool operator== (const DicomTag& other) const
+    {
+      return group_ == other.group_ && element_ == other.element_;
+    }
+
+    bool operator!= (const DicomTag& other) const
+    {
+      return !(*this == other);
+    }
+
+    std::string Format() const;
+
+    friend std::ostream& operator<< (std::ostream& o, const DicomTag& tag);
+
+    static void AddTagsForModule(std::set<DicomTag>& target,
+                                 DicomModule module);
+  };
+
+  // Aliases for the most useful tags
+  static const DicomTag DICOM_TAG_ACCESSION_NUMBER(0x0008, 0x0050);
+  static const DicomTag DICOM_TAG_SOP_INSTANCE_UID(0x0008, 0x0018);
+  static const DicomTag DICOM_TAG_PATIENT_ID(0x0010, 0x0020);
+  static const DicomTag DICOM_TAG_SERIES_INSTANCE_UID(0x0020, 0x000e);
+  static const DicomTag DICOM_TAG_STUDY_INSTANCE_UID(0x0020, 0x000d);
+  static const DicomTag DICOM_TAG_PIXEL_DATA(0x7fe0, 0x0010);
+
+  static const DicomTag DICOM_TAG_IMAGE_INDEX(0x0054, 0x1330);
+  static const DicomTag DICOM_TAG_INSTANCE_NUMBER(0x0020, 0x0013);
+
+  static const DicomTag DICOM_TAG_NUMBER_OF_SLICES(0x0054, 0x0081);
+  static const DicomTag DICOM_TAG_NUMBER_OF_TIME_SLICES(0x0054, 0x0101);
+  static const DicomTag DICOM_TAG_NUMBER_OF_FRAMES(0x0028, 0x0008);
+  static const DicomTag DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES(0x0018, 0x1090);
+  static const DicomTag DICOM_TAG_IMAGES_IN_ACQUISITION(0x0020, 0x1002);
+  static const DicomTag DICOM_TAG_PATIENT_NAME(0x0010, 0x0010);
+  static const DicomTag DICOM_TAG_ENCAPSULATED_DOCUMENT(0x0042, 0x0011);
+
+  static const DicomTag DICOM_TAG_STUDY_DESCRIPTION(0x0008, 0x1030);
+  static const DicomTag DICOM_TAG_SERIES_DESCRIPTION(0x0008, 0x103e);
+  static const DicomTag DICOM_TAG_MODALITY(0x0008, 0x0060);
+
+  // The following is used for "modify/anonymize" operations
+  static const DicomTag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016);
+  static const DicomTag DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID(0x0002, 0x0002);
+  static const DicomTag DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID(0x0002, 0x0003);
+  static const DicomTag DICOM_TAG_DEIDENTIFICATION_METHOD(0x0012, 0x0063);
+
+  // DICOM tags used for fMRI (thanks to Will Ryder)
+  static const DicomTag DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS(0x0020, 0x0105);
+  static const DicomTag DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER(0x0020, 0x0100);
+
+  // Tags for C-FIND and C-MOVE
+  static const DicomTag DICOM_TAG_SPECIFIC_CHARACTER_SET(0x0008, 0x0005);
+  static const DicomTag DICOM_TAG_QUERY_RETRIEVE_LEVEL(0x0008, 0x0052);
+  static const DicomTag DICOM_TAG_MODALITIES_IN_STUDY(0x0008, 0x0061);
+
+  // Tags for images
+  static const DicomTag DICOM_TAG_COLUMNS(0x0028, 0x0011);
+  static const DicomTag DICOM_TAG_ROWS(0x0028, 0x0010);
+  static const DicomTag DICOM_TAG_SAMPLES_PER_PIXEL(0x0028, 0x0002);
+  static const DicomTag DICOM_TAG_BITS_ALLOCATED(0x0028, 0x0100);
+  static const DicomTag DICOM_TAG_BITS_STORED(0x0028, 0x0101);
+  static const DicomTag DICOM_TAG_HIGH_BIT(0x0028, 0x0102);
+  static const DicomTag DICOM_TAG_PIXEL_REPRESENTATION(0x0028, 0x0103);
+  static const DicomTag DICOM_TAG_PLANAR_CONFIGURATION(0x0028, 0x0006);
+  static const DicomTag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004);
+  static const DicomTag DICOM_TAG_IMAGE_ORIENTATION_PATIENT(0x0020, 0x0037);
+  static const DicomTag DICOM_TAG_IMAGE_POSITION_PATIENT(0x0020, 0x0032);
+
+  // Tags related to date and time
+  static const DicomTag DICOM_TAG_ACQUISITION_DATE(0x0008, 0x0022);
+  static const DicomTag DICOM_TAG_ACQUISITION_TIME(0x0008, 0x0032);
+  static const DicomTag DICOM_TAG_CONTENT_DATE(0x0008, 0x0023);
+  static const DicomTag DICOM_TAG_CONTENT_TIME(0x0008, 0x0033);
+  static const DicomTag DICOM_TAG_INSTANCE_CREATION_DATE(0x0008, 0x0012);
+  static const DicomTag DICOM_TAG_INSTANCE_CREATION_TIME(0x0008, 0x0013);
+  static const DicomTag DICOM_TAG_PATIENT_BIRTH_DATE(0x0010, 0x0030);
+  static const DicomTag DICOM_TAG_PATIENT_BIRTH_TIME(0x0010, 0x0032);
+  static const DicomTag DICOM_TAG_SERIES_DATE(0x0008, 0x0021);
+  static const DicomTag DICOM_TAG_SERIES_TIME(0x0008, 0x0031);
+  static const DicomTag DICOM_TAG_STUDY_DATE(0x0008, 0x0020);
+  static const DicomTag DICOM_TAG_STUDY_TIME(0x0008, 0x0030);
+
+  // Various tags
+  static const DicomTag DICOM_TAG_SERIES_TYPE(0x0054, 0x1000);
+  static const DicomTag DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION(0x0032, 0x1060);
+  static const DicomTag DICOM_TAG_INSTITUTION_NAME(0x0008, 0x0080);
+  static const DicomTag DICOM_TAG_REQUESTING_PHYSICIAN(0x0032, 0x1032);
+  static const DicomTag DICOM_TAG_REFERRING_PHYSICIAN_NAME(0x0008, 0x0090);
+  static const DicomTag DICOM_TAG_OPERATOR_NAME(0x0008, 0x1070);
+  static const DicomTag DICOM_TAG_PERFORMED_PROCEDURE_STEP_DESCRIPTION(0x0040, 0x0254);
+  static const DicomTag DICOM_TAG_IMAGE_COMMENTS(0x0020, 0x4000);
+  static const DicomTag DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION(0x0018, 0x1400);
+  static const DicomTag DICOM_TAG_CONTRAST_BOLUS_AGENT(0x0018, 0x0010);
+
+  // Counting patients, studies and series
+  // https://www.medicalconnections.co.uk/kb/Counting_Studies_Series_and_Instances
+  static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES(0x0020, 0x1200);  
+  static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES(0x0020, 0x1202);  
+  static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES(0x0020, 0x1204);  
+  static const DicomTag DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES(0x0020, 0x1206);  
+  static const DicomTag DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES(0x0020, 0x1208);  
+  static const DicomTag DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES(0x0020, 0x1209);  
+  static const DicomTag DICOM_TAG_SOP_CLASSES_IN_STUDY(0x0008, 0x0062);  
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/Core/DicomFormat/DicomValue.cpp	Thu Jun 09 17:04:58 2016 +0200
@@ -0,0 +1,93 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "DicomValue.h"
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+
+namespace Orthanc
+{
+  DicomValue::DicomValue(const DicomValue& other) : 
+    type_(other.type_),
+    content_(other.content_)
+  {
+  }
+
+
+  DicomValue::DicomValue(const std::string& content,
+                         bool isBinary) :
+    type_(isBinary ? Type_Binary : Type_String),
+    content_(content)
+  {
+  }
+  
+  
+  DicomValue::DicomValue(const char* data,
+                         size_t size,
+                         bool isBinary) :
+    type_(isBinary ? Type_Binary : Type_String)
+  {
+    content_.assign(data, size);
+  }
+    
+  
+  const std::string& DicomValue::GetContent() const
+  {
+    if (type_ == Type_Null)
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+    else
+    {
+      return content_;
+    }
+  }
+
+
+  DicomValue* DicomValue::Clone() const
+  {
+    return new DicomValue(*this);
+  }
+
+  
+#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
+  void DicomValue::FormatDataUriScheme(std::string& target,
+                                       const std::string& mime) const
+  {
+    Toolbox::EncodeBase64(target, GetContent());
+    target.insert(0, "data:" + mime + ";base64,");
+  }
+#endif
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/Core/DicomFormat/DicomValue.h	Thu Jun 09 17:04:58 2016 +0200
@@ -0,0 +1,91 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <string>
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class DicomValue : public boost::noncopyable
+  {
+  private:
+    enum Type
+    {
+      Type_Null,
+      Type_String,
+      Type_Binary
+    };
+
+    Type         type_;
+    std::string  content_;
+
+    DicomValue(const DicomValue& other);
+
+  public:
+    DicomValue() : type_(Type_Null)
+    {
+    }
+    
+    DicomValue(const std::string& content,
+               bool isBinary);
+    
+    DicomValue(const char* data,
+               size_t size,
+               bool isBinary);
+    
+    const std::string& GetContent() const;
+
+    bool IsNull() const
+    {
+      return type_ == Type_Null;
+    }
+
+    bool IsBinary() const
+    {
+      return type_ == Type_Binary;
+    }
+    
+    DicomValue* Clone() const;
+
+#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
+    void FormatDataUriScheme(std::string& target,
+                             const std::string& mime) const;
+
+    void FormatDataUriScheme(std::string& target) const
+    {
+      FormatDataUriScheme(target, "application/octet-stream");
+    }
+#endif
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/Core/Endianness.h	Thu Jun 09 17:04:58 2016 +0200
@@ -0,0 +1,157 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+
+/********************************************************************
+ ** LINUX ARCHITECTURES
+ ********************************************************************/
+
+#if defined(__linux__)
+#  define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1
+#  include <endian.h>
+#endif
+
+
+/********************************************************************
+ ** WINDOWS ARCHITECTURES
+ **
+ ** On Windows x86, "host" will always be little-endian ("le").
+ ********************************************************************/
+
+#if defined(_WIN32)
+#  if defined(_MSC_VER)
+//   Visual Studio - http://msdn.microsoft.com/en-us/library/a3140177.aspx
+#    define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1
+#    define be16toh(x) _byteswap_ushort(x)
+#    define be32toh(x) _byteswap_ulong(x)
+#    define be64toh(x) _byteswap_uint64(x)
+#  elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))
+//   MinGW >= 4.3 - Use builtin intrinsic for byte swapping
+#    define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1
+#    define be16toh(x) __builtin_bswap16(x)
+#    define be32toh(x) __builtin_bswap32(x)
+#    define be64toh(x) __builtin_bswap64(x)
+#  else
+//   MinGW <= 4.2, we must manually implement the byte swapping
+#    define ORTHANC_HAS_BUILTIN_BYTE_SWAP 0
+#    define be16toh(x) __orthanc_bswap16(x)
+#    define be32toh(x) __orthanc_bswap32(x)
+#    define be64toh(x) __orthanc_bswap64(x)
+#  endif
+
+#  define htobe16(x) be16toh(x)
+#  define htobe32(x) be32toh(x)
+#  define htobe64(x) be64toh(x)
+
+#  define htole16(x) x
+#  define htole32(x) x
+#  define htole64(x) x
+
+#  define le16toh(x) x
+#  define le32toh(x) x
+#  define le64toh(x) x
+#endif
+
+
+/********************************************************************
+ ** FREEBSD ARCHITECTURES
+ ********************************************************************/
+
+#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+#  define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1
+#  include <arpa/inet.h>
+#endif
+
+
+/********************************************************************
+ ** APPLE ARCHITECTURES (including OS X)
+ ********************************************************************/
+
+#if defined(__APPLE__)
+#  define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1
+#  include <libkern/OSByteOrder.h>
+#  define be16toh(x) OSSwapBigToHostInt16(x)
+#  define be32toh(x) OSSwapBigToHostInt32(x)
+#  define be64toh(x) OSSwapBigToHostInt64(x)
+
+#  define htobe16(x) OSSwapHostToBigInt16(x)
+#  define htobe32(x) OSSwapHostToBigInt32(x)
+#  define htobe64(x) OSSwapHostToBigInt64(x)
+
+#  define htole16(x) OSSwapHostToLittleInt16(x)
+#  define htole32(x) OSSwapHostToLittleInt32(x)
+#  define htole64(x) OSSwapHostToLittleInt64(x)
+
+#  define le16toh(x) OSSwapLittleToHostInt16(x)
+#  define le32toh(x) OSSwapLittleToHostInt32(x)
+#  define le64toh(x) OSSwapLittleToHostInt64(x)
+#endif
+
+
+/********************************************************************
+ ** PORTABLE (BUT SLOW) IMPLEMENTATION OF BYTE-SWAPPING
+ ********************************************************************/
+
+#if ORTHANC_HAS_BUILTIN_BYTE_SWAP != 1
+
+#include <stdint.h>
+
+static inline uint16_t __orthanc_bswap16(uint16_t a)
+{
+  return (a << 8) | (a >> 8);
+}
+
+static inline uint32_t __orthanc_bswap32(uint32_t a)
+{
+  const uint8_t* p = reinterpret_cast<const uint8_t*>(&a);
+  return (static_cast<uint32_t>(p[0]) << 24 |
+          static_cast<uint32_t>(p[1]) << 16 |
+          static_cast<uint32_t>(p[2]) << 8 |
+          static_cast<uint32_t>(p[3]));
+}
+
+static inline uint64_t __orthanc_bswap64(uint64_t a)
+{
+  const uint8_t* p = reinterpret_cast<const uint8_t*>(&a);
+  return (static_cast<uint64_t>(p[0]) << 56 |
+          static_cast<uint64_t>(p[1]) << 48 |
+          static_cast<uint64_t>(p[2]) << 40 |
+          static_cast<uint64_t>(p[3]) << 32 |
+          static_cast<uint64_t>(p[4]) << 24 |
+          static_cast<uint64_t>(p[5]) << 16 |
+          static_cast<uint64_t>(p[6]) << 8 |
+          static_cast<uint64_t>(p[7]));
+}
+
+#endif
--- a/Orthanc/Core/Enumerations.cpp	Tue May 31 12:12:59 2016 +0200
+++ b/Orthanc/Core/Enumerations.cpp	Thu Jun 09 17:04:58 2016 +0200
@@ -35,6 +35,7 @@
 
 #include "OrthancException.h"
 #include "Toolbox.h"
+#include "Logging.h"
 
 #include <string.h>
 #include <cassert>
@@ -900,6 +901,151 @@
   }
 
 
+  ValueRepresentation StringToValueRepresentation(const std::string& vr,
+                                                  bool throwIfUnsupported)
+  {
+    if (vr == "AE")
+    {
+      return ValueRepresentation_ApplicationEntity;
+    }
+    else if (vr == "AS")
+    {
+      return ValueRepresentation_AgeString;
+    }
+    else if (vr == "AT")
+    {
+      return ValueRepresentation_AttributeTag;
+    }
+    else if (vr == "CS")
+    {
+      return ValueRepresentation_CodeString;
+    }
+    else if (vr == "DA")
+    {
+      return ValueRepresentation_Date;
+    }
+    else if (vr == "DS")
+    {
+      return ValueRepresentation_DecimalString;
+    }
+    else if (vr == "DT")
+    {
+      return ValueRepresentation_DateTime;
+    }
+    else if (vr == "FL")
+    {
+      return ValueRepresentation_FloatingPointSingle;
+    }
+    else if (vr == "FD")
+    {
+      return ValueRepresentation_FloatingPointDouble;
+    }
+    else if (vr == "IS")
+    {
+      return ValueRepresentation_IntegerString;
+    }
+    else if (vr == "LO")
+    {
+      return ValueRepresentation_LongString;
+    }
+    else if (vr == "LT")
+    {
+      return ValueRepresentation_LongText;
+    }
+    else if (vr == "OB")
+    {
+      return ValueRepresentation_OtherByte;
+    }
+    else if (vr == "OD")
+    {
+      return ValueRepresentation_OtherDouble;
+    }
+    else if (vr == "OF")
+    {
+      return ValueRepresentation_OtherFloat;
+    }
+    else if (vr == "OL")
+    {
+      return ValueRepresentation_OtherLong;
+    }
+    else if (vr == "OW")
+    {
+      return ValueRepresentation_OtherWord;
+    }
+    else if (vr == "PN")
+    {
+      return ValueRepresentation_PersonName;
+    }
+    else if (vr == "SH")
+    {
+      return ValueRepresentation_ShortString;
+    }
+    else if (vr == "SL")
+    {
+      return ValueRepresentation_SignedLong;
+    }
+    else if (vr == "SQ")
+    {
+      return ValueRepresentation_Sequence;
+    }
+    else if (vr == "SS")
+    {
+      return ValueRepresentation_SignedShort;
+    }
+    else if (vr == "ST")
+    {
+      return ValueRepresentation_ShortText;
+    }
+    else if (vr == "TM")
+    {
+      return ValueRepresentation_Time;
+    }
+    else if (vr == "UC")
+    {
+      return ValueRepresentation_UnlimitedCharacters;
+    }
+    else if (vr == "UI")
+    {
+      return ValueRepresentation_UniqueIdentifier;
+    }
+    else if (vr == "UL")
+    {
+      return ValueRepresentation_UnsignedLong;
+    }
+    else if (vr == "UN")
+    {
+      return ValueRepresentation_Unknown;
+    }
+    else if (vr == "UR")
+    {
+      return ValueRepresentation_UniversalResource;
+    }
+    else if (vr == "US")
+    {
+      return ValueRepresentation_UnsignedShort;
+    }
+    else if (vr == "UT")
+    {
+      return ValueRepresentation_UnlimitedText;
+    }
+    else
+    {
+      std::string s = "Unsupported value representation encountered: " + vr;
+
+      if (throwIfUnsupported)
+      {
+        LOG(ERROR) << s;
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+      else
+      {
+        LOG(INFO) << s;
+        return ValueRepresentation_NotSupported;
+      }
+    }
+  }
+
+
   unsigned int GetBytesPerPixel(PixelFormat format)
   {
     switch (format)
@@ -1187,4 +1333,60 @@
     return (type >= FileContentType_StartUser &&
             type <= FileContentType_EndUser);
   }
+
+
+  bool IsBinaryValueRepresentation(ValueRepresentation vr)
+  {
+    // http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html
+
+    switch (vr)
+    {
+      case ValueRepresentation_ApplicationEntity:     // AE
+      case ValueRepresentation_AgeString:             // AS
+      case ValueRepresentation_CodeString:            // CS
+      case ValueRepresentation_Date:                  // DA
+      case ValueRepresentation_DecimalString:         // DS
+      case ValueRepresentation_DateTime:              // DT
+      case ValueRepresentation_IntegerString:         // IS
+      case ValueRepresentation_LongString:            // LO
+      case ValueRepresentation_LongText:              // LT
+      case ValueRepresentation_PersonName:            // PN
+      case ValueRepresentation_ShortString:           // SH
+      case ValueRepresentation_ShortText:             // ST
+      case ValueRepresentation_Time:                  // TM
+      case ValueRepresentation_UnlimitedCharacters:   // UC
+      case ValueRepresentation_UniqueIdentifier:      // UI (UID)
+      case ValueRepresentation_UniversalResource:     // UR (URI or URL)
+      case ValueRepresentation_UnlimitedText:         // UT
+      {
+        return false;
+      }
+
+      /**
+       * Below are all the VR whose character repertoire is tagged as
+       * "not applicable"
+       **/
+      case ValueRepresentation_AttributeTag:          // AT (2 x uint16_t)
+      case ValueRepresentation_FloatingPointSingle:   // FL (float)
+      case ValueRepresentation_FloatingPointDouble:   // FD (double)
+      case ValueRepresentation_OtherByte:             // OB
+      case ValueRepresentation_OtherDouble:           // OD
+      case ValueRepresentation_OtherFloat:            // OF
+      case ValueRepresentation_OtherLong:             // OL
+      case ValueRepresentation_OtherWord:             // OW
+      case ValueRepresentation_SignedLong:            // SL (int32_t)
+      case ValueRepresentation_Sequence:              // SQ
+      case ValueRepresentation_SignedShort:           // SS (int16_t)
+      case ValueRepresentation_UnsignedLong:          // UL (uint32_t)
+      case ValueRepresentation_Unknown:               // UN
+      case ValueRepresentation_UnsignedShort:         // US (uint16_t)
+      {
+        return true;
+      }
+
+      case ValueRepresentation_NotSupported:
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
 }
--- a/Orthanc/Core/Enumerations.h	Tue May 31 12:12:59 2016 +0200
+++ b/Orthanc/Core/Enumerations.h	Thu Jun 09 17:04:58 2016 +0200
@@ -32,6 +32,8 @@
 
 #pragma once
 
+#include <string>
+
 namespace Orthanc
 {
   enum Endianness
@@ -385,6 +387,48 @@
 
 
   /**
+   * The value representations Orthanc knows about. They correspond to
+   * the DICOM 2016b version of the standard.
+   * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html
+   **/
+  enum ValueRepresentation
+  {
+    ValueRepresentation_ApplicationEntity = 1,     // AE
+    ValueRepresentation_AgeString = 2,             // AS
+    ValueRepresentation_AttributeTag = 3,          // AT (2 x uint16_t)
+    ValueRepresentation_CodeString = 4,            // CS
+    ValueRepresentation_Date = 5,                  // DA
+    ValueRepresentation_DecimalString = 6,         // DS
+    ValueRepresentation_DateTime = 7,              // DT
+    ValueRepresentation_FloatingPointSingle = 8,   // FL (float)
+    ValueRepresentation_FloatingPointDouble = 9,   // FD (double)
+    ValueRepresentation_IntegerString = 10,        // IS
+    ValueRepresentation_LongString = 11,           // LO
+    ValueRepresentation_LongText = 12,             // LT
+    ValueRepresentation_OtherByte = 13,            // OB
+    ValueRepresentation_OtherDouble = 14,          // OD
+    ValueRepresentation_OtherFloat = 15,           // OF
+    ValueRepresentation_OtherLong = 16,            // OL
+    ValueRepresentation_OtherWord = 17,            // OW
+    ValueRepresentation_PersonName = 18,           // PN
+    ValueRepresentation_ShortString = 19,          // SH
+    ValueRepresentation_SignedLong = 20,           // SL (int32_t)
+    ValueRepresentation_Sequence = 21,             // SQ
+    ValueRepresentation_SignedShort = 22,          // SS (int16_t)
+    ValueRepresentation_ShortText = 23,            // ST
+    ValueRepresentation_Time = 24,                 // TM
+    ValueRepresentation_UnlimitedCharacters = 25,  // UC
+    ValueRepresentation_UniqueIdentifier = 26,     // UI (UID)
+    ValueRepresentation_UnsignedLong = 27,         // UL (uint32_t)
+    ValueRepresentation_Unknown = 28,              // UN
+    ValueRepresentation_UniversalResource = 29,    // UR (URI or URL)
+    ValueRepresentation_UnsignedShort = 30,        // US (uint16_t)
+    ValueRepresentation_UnlimitedText = 31,        // UT
+    ValueRepresentation_NotSupported               // Not supported by Orthanc, or tag not in dictionary
+  };
+
+
+  /**
    * WARNING: Do not change the explicit values in the enumerations
    * below this point. This would result in incompatible databases
    * between versions of Orthanc!
@@ -459,7 +503,10 @@
 
   ImageFormat StringToImageFormat(const char* format);
 
-  LogLevel StringToLogLevel(const char* format);
+  LogLevel StringToLogLevel(const char* level);
+
+  ValueRepresentation StringToValueRepresentation(const std::string& vr,
+                                                  bool throwIfUnsupported);
 
   unsigned int GetBytesPerPixel(PixelFormat format);
 
@@ -477,4 +524,6 @@
   HttpStatus ConvertErrorCodeToHttpStatus(ErrorCode error);
 
   bool IsUserContentType(FileContentType type);
+
+  bool IsBinaryValueRepresentation(ValueRepresentation vr);
 }
--- a/Orthanc/Resources/CMake/DownloadPackage.cmake	Tue May 31 12:12:59 2016 +0200
+++ b/Orthanc/Resources/CMake/DownloadPackage.cmake	Thu Jun 09 17:04:58 2016 +0200
@@ -83,7 +83,9 @@
       # How to silently extract files using 7-zip
       # http://superuser.com/questions/331148/7zip-command-line-extract-silently-quietly
 
-      if (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz"))
+      if (("${TMP_EXTENSION}" STREQUAL "gz") OR 
+          ("${TMP_EXTENSION}" STREQUAL "tgz") OR
+          ("${TMP_EXTENSION}" STREQUAL "xz"))
         execute_process(
           COMMAND ${ZIP_EXECUTABLE} e -y ${TMP_PATH}
           WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
@@ -97,8 +99,10 @@
 
         if ("${TMP_EXTENSION}" STREQUAL "tgz")
           string(REGEX REPLACE ".tgz$" ".tar" TMP_FILENAME2 "${TMP_FILENAME}")
-        else()
+        elseif ("${TMP_EXTENSION}" STREQUAL "gz")
           string(REGEX REPLACE ".gz$" "" TMP_FILENAME2 "${TMP_FILENAME}")
+        elseif ("${TMP_EXTENSION}" STREQUAL "xz")
+          string(REGEX REPLACE ".xz" "" TMP_FILENAME2 "${TMP_FILENAME}")
         endif()
 
         execute_process(
@@ -138,6 +142,12 @@
           WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
           RESULT_VARIABLE Failure
           )
+      elseif ("${TMP_EXTENSION}" STREQUAL "xz")
+        execute_process(
+          COMMAND sh -c "${TAR_EXECUTABLE} xf ${TMP_PATH}"
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          )
       else()
         message(FATAL_ERROR "Unknown package format.")
       endif()
--- a/Plugin/Plugin.cpp	Tue May 31 12:12:59 2016 +0200
+++ b/Plugin/Plugin.cpp	Thu Jun 09 17:04:58 2016 +0200
@@ -24,6 +24,7 @@
 #include <boost/filesystem.hpp>
 
 #include "../Orthanc/Core/OrthancException.h"
+#include "../Orthanc/Core/DicomFormat/DicomMap.h"
 #include "ViewerToolbox.h"
 #include "ViewerPrefetchPolicy.h"
 #include "DecodedImageAdapter.h"
@@ -33,7 +34,8 @@
 
 
 static OrthancPluginContext* context_ = NULL;
-
+static bool restrictTransferSyntaxes_ = false;
+static std::set<std::string> enabledTransferSyntaxes_;
 
 
 class CacheContext
@@ -323,6 +325,80 @@
 }
 
 
+
+static bool ExtractTransferSyntax(std::string& transferSyntax,
+                                  const void* dicom,
+                                  const uint32_t size)
+{
+  Orthanc::DicomMap header;
+  if (!Orthanc::DicomMap::ParseDicomMetaInformation(header, reinterpret_cast<const char*>(dicom), size))
+  {
+    return false;
+  }
+
+  const Orthanc::DicomValue* tag = header.TestAndGetValue(0x0002, 0x0010);
+  if (tag == NULL ||
+      tag->IsNull() ||
+      tag->IsBinary())
+  {
+    return false;
+  }
+  else
+  {
+    // Stripping spaces should not be required, as this is a UI value
+    // representation whose stripping is supported by the Orthanc
+    // core, but let's be careful...
+    transferSyntax = Orthanc::Toolbox::StripSpaces(tag->GetContent());
+    return true;
+  }
+}
+
+
+static bool IsTransferSyntaxEnabled(const void* dicom,
+                                    const uint32_t size)
+{
+  std::string formattedSize;
+
+  {
+    char tmp[16];
+    sprintf(tmp, "%0.1fMB", static_cast<float>(size) / (1024.0f * 1024.0f));
+    formattedSize.assign(tmp);
+  }
+
+  if (!restrictTransferSyntaxes_)
+  {
+    std::string s = "Decoding one DICOM instance of " + formattedSize + " using GDCM";
+    OrthancPluginLogInfo(context_, s.c_str());
+    return true;
+  }
+
+  std::string transferSyntax;
+  if (!ExtractTransferSyntax(transferSyntax, dicom, size))
+  {
+    std::string s = ("Cannot extract the transfer syntax of this instance of " + 
+                     formattedSize + ", will use GDCM to decode it");
+    OrthancPluginLogInfo(context_, s.c_str());
+    return true;
+  }
+
+  if (enabledTransferSyntaxes_.find(transferSyntax) != enabledTransferSyntaxes_.end())
+  {
+    // Decoding for this transfer syntax is enabled
+    std::string s = ("Using GDCM to decode this instance of " + 
+                     formattedSize + " with transfer syntax " + transferSyntax);
+    OrthancPluginLogInfo(context_, s.c_str());
+    return true;
+  }
+  else
+  {
+    std::string s = ("Won't use GDCM to decode this instance of " + 
+                     formattedSize + ", as its transfer syntax " + transferSyntax + " is disabled");
+    OrthancPluginLogInfo(context_, s.c_str());
+    return false;
+  }
+}
+
+
 static OrthancPluginErrorCode DecodeImageCallback(OrthancPluginImage** target,
                                                   const void* dicom,
                                                   const uint32_t size,
@@ -330,6 +406,12 @@
 {
   try
   {
+    if (!IsTransferSyntaxEnabled(dicom, size))
+    {
+      *target = NULL;
+      return OrthancPluginErrorCode_Success;
+    }
+
     std::auto_ptr<OrthancPlugins::OrthancImageWrapper> image;
 
 #if 0
@@ -364,6 +446,91 @@
 }
 
 
+void ParseConfiguration(bool& enableGdcm,
+                        int& decodingThreads,
+                        boost::filesystem::path& cachePath,
+                        int& cacheSize)
+{
+  /* Read the configuration of the Web viewer */
+  Json::Value configuration;
+  if (!OrthancPlugins::ReadConfiguration(configuration, context_))
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);    
+  }
+
+  // By default, the cache of the Web viewer is located inside the
+  // "StorageDirectory" of Orthanc
+  cachePath = OrthancPlugins::GetStringValue(configuration, "StorageDirectory", ".");
+  cachePath /= "WebViewerCache";
+
+  static const char* CONFIG_WEB_VIEWER = "WebViewer";
+  if (configuration.isMember(CONFIG_WEB_VIEWER))
+  {
+
+    std::string key = "CachePath";
+    if (!configuration[CONFIG_WEB_VIEWER].isMember(key))
+    {
+      // For backward compatibility with the initial release of the Web viewer
+      key = "Cache";
+    }
+
+    cachePath = OrthancPlugins::GetStringValue(configuration[CONFIG_WEB_VIEWER], key, cachePath.string());
+    cacheSize = OrthancPlugins::GetIntegerValue(configuration[CONFIG_WEB_VIEWER], "CacheSize", cacheSize);
+    decodingThreads = OrthancPlugins::GetIntegerValue(configuration[CONFIG_WEB_VIEWER], "Threads", decodingThreads);
+
+    static const char* CONFIG_ENABLE_GDCM = "EnableGdcm";
+    if (configuration[CONFIG_WEB_VIEWER].isMember(CONFIG_ENABLE_GDCM))
+    {
+      if (configuration[CONFIG_WEB_VIEWER][CONFIG_ENABLE_GDCM].type() != Json::booleanValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+      else
+      {
+        enableGdcm = configuration[CONFIG_WEB_VIEWER][CONFIG_ENABLE_GDCM].asBool();
+      }
+    }
+
+    static const char* CONFIG_RESTRICT_TRANSFER_SYNTAXES = "RestrictTransferSyntaxes";
+    if (enableGdcm)
+    {
+      if (configuration[CONFIG_WEB_VIEWER].isMember(CONFIG_RESTRICT_TRANSFER_SYNTAXES))
+      {
+        const Json::Value& config = configuration[CONFIG_WEB_VIEWER][CONFIG_RESTRICT_TRANSFER_SYNTAXES];
+
+        if (config.type() != Json::arrayValue)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+
+        restrictTransferSyntaxes_ = true;
+        for (Json::Value::ArrayIndex i = 0; i < config.size(); i++)
+        {
+          if (config[i].type() != Json::stringValue)
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+          }
+          else
+          {
+            std::string s = "Web viewer will use GDCM to decode transfer syntax " + config[i].asString();
+            enabledTransferSyntaxes_.insert(config[i].asString());
+            OrthancPluginLogWarning(context_, s.c_str());
+          }
+        }
+      }
+    }
+  }
+
+  if (decodingThreads <= 0 ||
+      cacheSize <= 0)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+  }
+
+}
+
+
+
 extern "C"
 {
   ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
@@ -400,53 +567,18 @@
     /* By default, use GDCM */
     bool enableGdcm = true;
 
+    /* By default, a cache of 100 MB is used */
+    int cacheSize = 100; 
 
     try
     {
-      /* Read the configuration of the Web viewer */
-      Json::Value configuration;
-      if (!ReadConfiguration(configuration, context))
-      {
-        OrthancPluginLogError(context_, "Unable to read the configuration file of Orthanc");
-        return -1;
-      }
-
-      // By default, the cache of the Web viewer is located inside the
-      // "StorageDirectory" of Orthanc
-      boost::filesystem::path cachePath = GetStringValue(configuration, "StorageDirectory", ".");
-      cachePath /= "WebViewerCache";
-      int cacheSize = 100;  // By default, a cache of 100 MB is used
-
-      if (configuration.isMember("WebViewer"))
-      {
-        std::string key = "CachePath";
-        if (!configuration["WebViewer"].isMember(key))
-        {
-          // For backward compatibility with the initial release of the Web viewer
-          key = "Cache";
-        }
-
-        cachePath = GetStringValue(configuration["WebViewer"], key, cachePath.string());
-        cacheSize = GetIntegerValue(configuration["WebViewer"], "CacheSize", cacheSize);
-        decodingThreads = GetIntegerValue(configuration["WebViewer"], "Threads", decodingThreads);
-
-        if (configuration["WebViewer"].isMember("EnableGdcm") &&
-            configuration["WebViewer"]["EnableGdcm"].type() == Json::booleanValue)
-        {
-          enableGdcm = configuration["WebViewer"]["EnableGdcm"].asBool();
-        }
-      }
+      boost::filesystem::path cachePath;
+      ParseConfiguration(enableGdcm, decodingThreads, cachePath, cacheSize);
 
       std::string message = ("Web viewer using " + boost::lexical_cast<std::string>(decodingThreads) + 
                              " threads for the decoding of the DICOM images");
       OrthancPluginLogWarning(context_, message.c_str());
 
-      if (decodingThreads <= 0 ||
-          cacheSize <= 0)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-
       message = "Storing the cache of the Web viewer in folder: " + cachePath.string();
       OrthancPluginLogWarning(context_, message.c_str());
 
@@ -515,7 +647,14 @@
     }
     catch (Orthanc::OrthancException& e)
     {
-      OrthancPluginLogError(context_, e.What());
+      if (e.GetErrorCode() == Orthanc::ErrorCode_BadFileFormat)
+      {
+        OrthancPluginLogError(context_, "Unable to read the configuration of the Web viewer plugin");
+      }
+      else
+      {
+        OrthancPluginLogError(context_, e.What());
+      }
       return -1;
     }
 
--- a/Resources/SyncOrthancFolder.py	Tue May 31 12:12:59 2016 +0200
+++ b/Resources/SyncOrthancFolder.py	Thu Jun 09 17:04:58 2016 +0200
@@ -19,6 +19,13 @@
     'Core/ChunkedBuffer.h',
     'Core/Enumerations.cpp',
     'Core/Enumerations.h',
+    'Core/Endianness.h',
+    'Core/DicomFormat/DicomMap.h',
+    'Core/DicomFormat/DicomMap.cpp',
+    'Core/DicomFormat/DicomTag.h',
+    'Core/DicomFormat/DicomTag.cpp',
+    'Core/DicomFormat/DicomValue.h',
+    'Core/DicomFormat/DicomValue.cpp',
     'Core/FileStorage/FilesystemStorage.cpp',
     'Core/FileStorage/FilesystemStorage.h',
     'Core/FileStorage/IStorageArea.h',