changeset 1874:950745f3f48b dcmtk-3.6.1

integration mainline->dcmtk-3.6.1
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 07 Dec 2015 10:39:23 +0100
parents 483f26479743 (current diff) 5e7feeb63d1f (diff)
children 7bef560b9782
files
diffstat 23 files changed, 433 insertions(+), 66 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Wed Dec 02 10:05:45 2015 +0100
+++ b/NEWS	Mon Dec 07 10:39:23 2015 +0100
@@ -1,11 +1,20 @@
 Pending changes in the mainline
 ===============================
 
+* Fix modality worklists server if some fields are null
+* More tolerant "/series/.../ordered-slices" with broken series
+* Promiscuous mode is now turned off by default
+* Improved logging information if upgrade fails
+
+
+Version 0.9.5 (2015/12/02)
+==========================
+
 Major
 -----
 
-* Experimental support of DICOM C-Find SCP for modality worklists through plugins
-* Support of DICOM C-Find SCU for modality worklists ("/modalities/{dicom}/find-worklist")
+* Experimental support of DICOM C-FIND SCP for modality worklists through plugins
+* Support of DICOM C-FIND SCU for modality worklists ("/modalities/{dicom}/find-worklist")
 
 REST API
 --------
@@ -43,6 +52,7 @@
   - "OrthancPluginDicomBufferToJson()" to convert DICOM to JSON
   - "OrthancPluginRegisterErrorCode()" to declare custom error codes
   - "OrthancPluginRegisterDictionaryTag()" to declare custom DICOM tags
+  - "OrthancPluginLookupDictionary()" to get information about some DICOM tag
   - "OrthancPluginRestApiGet2()" to provide HTTP headers when calling Orthanc API
   - "OrthancPluginGetInstanceOrigin()" to know through which mechanism an instance was received
   - "OrthancPluginCreateImage()" and "OrthancPluginCreateImageAccessor()" to create images
@@ -64,15 +74,16 @@
 Maintenance
 -----------
 
-* Full indexation of the patient/study tags to speed up searches and C-Find
+* Full indexation of the patient/study tags to speed up searches and C-FIND
 * Many refactorings, notably of the searching features and of the image decoding
-* C-Move SCP for studies using AccessionNumber tag
-* Fix issue 4 (C-Store Association not renegotiated on Specific-to-specific transfer syntax change)
+* C-MOVE SCP for studies using AccessionNumber tag
+* Fix issue 4 (C-STORE Association not renegotiated on Specific-to-specific transfer syntax change)
 * Fix formatting of multipart HTTP answers
 * "--logdir" flag creates a single log file instead of 3 separate files for errors/warnings/infos
 * "--errors" flag lists the error codes that could be returned by Orthanc
 * Under Windows, the exit status of Orthanc corresponds to the encountered error code
 * New "AgfaImpax", "EFilm2" and "Vitrea" modality manufacturers
+* C-FIND SCP will return tags with sequence value representation
 * Upgrade to Boost 1.59.0 for static builds
 
 
@@ -148,7 +159,7 @@
 -------
 
 * The configuration can be splitted into several files stored inside the same folder
-* Custom setting of the local AET during C-Store SCU (both in Lua and in the REST API)
+* Custom setting of the local AET during C-STORE SCU (both in Lua and in the REST API)
 * Many code refactorings
 
 Lua
@@ -167,9 +178,9 @@
 Fixes
 -----
 
-* Fix compatibility issues for C-Find SCU to Siemens Syngo.Via modalities SCP
+* Fix compatibility issues for C-FIND SCU to Siemens Syngo.Via modalities SCP
 * Fix issue 15 (Lua scripts making HTTP requests)
-* Fix issue 35 (Characters in PatientID string are not protected for C-Find)
+* Fix issue 35 (Characters in PatientID string are not protected for C-FIND)
 * Fix issue 37 (Hyphens trigger range query even if datatype does not support ranges)
 
 
@@ -180,7 +191,7 @@
 -----
 
 * DICOM Query/Retrieve available from Orthanc Explorer
-* C-Move SCU and C-Find SCU are accessible through the REST API
+* C-MOVE SCU and C-FIND SCU are accessible through the REST API
 * "?expand" flag for URIs "/patients", "/studies" and "/series"
 * "/tools/find" URI to search for DICOM resources from REST
 * Support of FreeBSD
@@ -190,15 +201,15 @@
 -----
 
 * Speed-up in Orthanc Explorer for large amount of images
-* Speed-up of the C-Find SCP server of Orthanc
+* Speed-up of the C-FIND SCP server of Orthanc
 * Allow replacing PatientID/StudyInstanceUID/SeriesInstanceUID from Lua scripts
-* Option "CaseSensitivePN" to enable case-insensitive C-Find SCP
+* Option "CaseSensitivePN" to enable case-insensitive C-FIND SCP
 
 Fixes
 -----
 
 * Prevent freeze on C-FIND if no DICOM tag is to be returned
-* Fix slow C-Store SCP on recent versions of Linux, if
+* Fix slow C-STORE SCP on recent versions of Linux, if
   USE_SYSTEM_DCMTK is set to OFF (http://forum.dcmtk.org/viewtopic.php?f=1&t=4009)
 * Fix issue 30 (QR response missing "Query/Retrieve Level" (008,0052))
 * Fix issue 32 (Cyrillic symbols): Introduction of the "Windows1251" encoding
@@ -269,7 +280,7 @@
 
 * "/instances-tags" to get the tags of all the child instances of a
   patient/study/series with a single REST call (bulk tags retrieval)
-* Configuration/Lua to select the accepted C-Store SCP transfer syntaxes
+* Configuration/Lua to select the accepted C-STORE SCP transfer syntaxes
 * Fix reporting of errors in Orthanc Explorer when sending images to peers/modalities
 * Installation of plugin SDK in CMake
 
@@ -380,7 +391,7 @@
 Version 0.7.5 (2014/05/08)
 ==========================
 
-* Dynamic negotiation of SOP classes for C-Store SCU
+* Dynamic negotiation of SOP classes for C-STORE SCU
 * Creation of DICOM instances using the REST API
 * Embedding of images within DICOM instances
 * Adding/removal/modification of remote modalities/peers through REST
@@ -427,8 +438,8 @@
 ==========================
 
 * Support of Query/Retrieve from medInria
-* Accept more transfer syntaxes for C-Store SCP and SCU (notably JPEG)
-* Create the meta-header when receiving files through C-Store SCP
+* Accept more transfer syntaxes for C-STORE SCP and SCU (notably JPEG)
+* Create the meta-header when receiving files through C-STORE SCP
 * Fixes and improvements thanks to the static analyzer cppcheck
 
 
@@ -471,7 +482,7 @@
 ==========================
 
 * Detection of stable patients/studies/series
-* C-Find SCU at the instance level
+* C-FIND SCU at the instance level
 * Link from modified to original resource in Orthanc Explorer
 * Fix of issue #8
 * Anonymization of the medical alerts tag (0010,2000)
@@ -581,7 +592,7 @@
 * The patient/study/series/instances are now indexed by SHA-1 digests
   of their DICOM Instance IDs (and not by UUIDs anymore): The same
   DICOM objects are thus always identified by the same Orthanc IDs
-* Log of exported instances through DICOM C-Store SCU ("/exported" URI)
+* Log of exported instances through DICOM C-STORE SCU ("/exported" URI)
 * Full refactoring of the DB schema and of the REST API
 * Introduction of generic classes for REST APIs (in Core/RestApi)
 
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.cpp	Wed Dec 02 10:05:45 2015 +0100
+++ b/OrthancServer/DicomProtocol/DicomFindAnswers.cpp	Mon Dec 07 10:39:23 2015 +0100
@@ -189,7 +189,7 @@
                                 size_t index,
                                 bool simplify) const
   {
-    DicomToJsonFormat format = (simplify ? DicomToJsonFormat_Simple : DicomToJsonFormat_Full);
+    DicomToJsonFormat format = (simplify ? DicomToJsonFormat_Human : DicomToJsonFormat_Full);
     GetAnswer(index).ToJson(target, format, DicomToJsonFlags_None, 0);
   }
 
--- a/OrthancServer/DicomProtocol/IFindRequestHandler.h	Wed Dec 02 10:05:45 2015 +0100
+++ b/OrthancServer/DicomProtocol/IFindRequestHandler.h	Mon Dec 07 10:39:23 2015 +0100
@@ -45,6 +45,7 @@
 
     virtual void Handle(DicomFindAnswers& answers,
                         const DicomMap& input,
+                        const std::list<DicomTag>& sequencesToReturn,
                         const std::string& remoteIp,
                         const std::string& remoteAet,
                         const std::string& calledAet) = 0;
--- a/OrthancServer/FromDcmtkBridge.cpp	Wed Dec 02 10:05:45 2015 +0100
+++ b/OrthancServer/FromDcmtkBridge.cpp	Mon Dec 07 10:39:23 2015 +0100
@@ -581,7 +581,7 @@
     
     switch (format)
     {
-      case DicomToJsonFormat_Simple:
+      case DicomToJsonFormat_Human:
         parent[tagName] = Json::nullValue;
         return parent[tagName];
 
@@ -628,7 +628,7 @@
     switch (format)
     {
       case DicomToJsonFormat_Short:
-      case DicomToJsonFormat_Simple:
+      case DicomToJsonFormat_Human:
       {
         assert(target.type() == Json::nullValue);
         targetValue = &target;
@@ -880,6 +880,21 @@
       return DicomTag(group, element);
     }
 
+    if (strlen(name) == 8 &&
+        isxdigit(name[0]) &&
+        isxdigit(name[1]) &&
+        isxdigit(name[2]) &&
+        isxdigit(name[3]) &&
+        isxdigit(name[4]) &&
+        isxdigit(name[5]) &&
+        isxdigit(name[6]) &&
+        isxdigit(name[7]))        
+    {
+      uint16_t group = GetTagValue(name);
+      uint16_t element = GetTagValue(name + 4);
+      return DicomTag(group, element);
+    }
+
 #if 0
     const DcmDataDictionary& dict = dcmDataDict.rdlock();
     const DcmDictEntry* entry = dict.findEntry(name);
--- a/OrthancServer/Internals/FindScp.cpp	Wed Dec 02 10:05:45 2015 +0100
+++ b/OrthancServer/Internals/FindScp.cpp	Mon Dec 07 10:39:23 2015 +0100
@@ -148,9 +148,30 @@
           {
             if (data.findHandler_ != NULL)
             {
+              std::list<DicomTag> sequencesToReturn;
+
+              for (unsigned long i = 0; i < requestIdentifiers->card(); i++)
+              {
+                DcmElement* element = requestIdentifiers->getElement(i);
+                if (element && !element->isLeaf())
+                {
+                  const DicomTag tag(FromDcmtkBridge::Convert(element->getTag()));
+
+                  DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element);
+                  if (sequence.card() != 0)
+                  {
+                    LOG(WARNING) << "Orthanc only supports sequence matching on worklists, "
+                                 << "ignoring C-FIND SCU constraint on tag (" << tag.Format() 
+                                 << ") " << FromDcmtkBridge::GetName(tag);
+                  }
+
+                  sequencesToReturn.push_back(tag);
+                }
+              }
+
               DicomMap input;
               FromDcmtkBridge::Convert(input, *requestIdentifiers);
-              data.findHandler_->Handle(data.answers_, input,
+              data.findHandler_->Handle(data.answers_, input, sequencesToReturn,
                                         *data.remoteIp_, *data.remoteAet_,
                                         *data.calledAet_);
               ok = true;
--- a/OrthancServer/OrthancFindRequestHandler.cpp	Wed Dec 02 10:05:45 2015 +0100
+++ b/OrthancServer/OrthancFindRequestHandler.cpp	Mon Dec 07 10:39:23 2015 +0100
@@ -47,19 +47,21 @@
 {
   static void AddAnswer(DicomFindAnswers& answers,
                         const Json::Value& resource,
-                        const DicomArray& query)
+                        const DicomArray& query,
+                        const std::list<DicomTag>& sequencesToReturn)
   {
     DicomMap result;
 
     for (size_t i = 0; i < query.GetSize(); i++)
     {
-      // Fix issue 30 (QR response missing "Query/Retrieve Level" (008,0052))
       if (query.GetElement(i).GetTag() == DICOM_TAG_QUERY_RETRIEVE_LEVEL)
       {
+        // Fix issue 30 on Google Code (QR response missing "Query/Retrieve Level" (008,0052))
         result.SetValue(query.GetElement(i).GetTag(), query.GetElement(i).GetValue());
       }
       else if (query.GetElement(i).GetTag() == DICOM_TAG_SPECIFIC_CHARACTER_SET)
       {
+        // Do not include the encoding, this is handled by class ParsedDicomFile
       }
       else
       {
@@ -77,13 +79,46 @@
       }
     }
 
-    if (result.GetSize() == 0)
+    if (result.GetSize() == 0 &&
+        sequencesToReturn.empty())
     {
       LOG(WARNING) << "The C-FIND request does not return any DICOM tag";
     }
+    else if (sequencesToReturn.empty())
+    {
+      answers.Add(result);
+    }
     else
     {
-      answers.Add(result);
+      ParsedDicomFile dicom(result);
+
+      for (std::list<DicomTag>::const_iterator tag = sequencesToReturn.begin();
+           tag != sequencesToReturn.end(); ++tag)
+      {
+        std::cout << tag->Format();
+
+        const Json::Value& source = resource[tag->Format()];
+
+        if (source.type() == Json::objectValue &&
+            source.isMember("Type") &&
+            source.isMember("Value") &&
+            source["Type"].asString() == "Sequence" &&
+            source["Value"].type() == Json::arrayValue)
+        {
+          Json::Value content = Json::arrayValue;
+
+          for (Json::Value::ArrayIndex i = 0; i < source["Value"].size(); i++)
+          {
+            Json::Value item;
+            Toolbox::SimplifyTags(item, source["Value"][i], DicomToJsonFormat_Short);
+            content.append(item);
+          }
+
+          dicom.Replace(*tag, content, false);
+        }
+      }
+
+      answers.Add(dicom);
     }
   }
 
@@ -126,6 +161,7 @@
 
   void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers,
                                          const DicomMap& input,
+                                         const std::list<DicomTag>& sequencesToReturn,
                                          const std::string& remoteIp,
                                          const std::string& remoteAet,
                                          const std::string& calledAet)
@@ -181,6 +217,14 @@
       }
     }
 
+    for (std::list<DicomTag>::const_iterator it = sequencesToReturn.begin();
+         it != sequencesToReturn.end(); ++it)
+    {
+      LOG(INFO) << "  (" << it->Format()
+                << ")  " << FromDcmtkBridge::GetName(*it)
+                << " : sequence tag whose content will be copied";
+    }
+
 
     /**
      * Build up the query object.
@@ -255,7 +299,7 @@
         }
         else
         {
-          AddAnswer(answers, dicom, query);
+          AddAnswer(answers, dicom, query, sequencesToReturn);
         }
       }
     }
--- a/OrthancServer/OrthancFindRequestHandler.h	Wed Dec 02 10:05:45 2015 +0100
+++ b/OrthancServer/OrthancFindRequestHandler.h	Mon Dec 07 10:39:23 2015 +0100
@@ -62,6 +62,7 @@
 
     virtual void Handle(DicomFindAnswers& answers,
                         const DicomMap& input,
+                        const std::list<DicomTag>& sequencesToReturn,
                         const std::string& remoteIp,
                         const std::string& remoteAet,
                         const std::string& calledAet);
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Wed Dec 02 10:05:45 2015 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Mon Dec 07 10:39:23 2015 +0100
@@ -209,7 +209,7 @@
       context.ReadJson(full, publicId);
 
       Json::Value simplified;
-      Toolbox::SimplifyTags(simplified, full);
+      Toolbox::SimplifyTags(simplified, full, DicomToJsonFormat_Human);
       call.GetOutput().AnswerJson(simplified);
     }
     else
@@ -928,7 +928,7 @@
       if (simplify)
       {
         Json::Value simplified;
-        Toolbox::SimplifyTags(simplified, sharedTags);
+        Toolbox::SimplifyTags(simplified, sharedTags, DicomToJsonFormat_Human);
         call.GetOutput().AnswerJson(simplified);
       }
       else
@@ -995,7 +995,7 @@
     if (simplify)
     {
       Json::Value simplified;
-      Toolbox::SimplifyTags(simplified, result);
+      Toolbox::SimplifyTags(simplified, result, DicomToJsonFormat_Human);
       call.GetOutput().AnswerJson(simplified);
     }
     else
@@ -1201,7 +1201,7 @@
       if (simplify)
       {
         Json::Value simplified;
-        Toolbox::SimplifyTags(simplified, full);
+        Toolbox::SimplifyTags(simplified, full, DicomToJsonFormat_Human);
         result[*it] = simplified;
       }
       else
@@ -1295,7 +1295,7 @@
     if (simplify)
     {
       Json::Value simplified;
-      Toolbox::SimplifyTags(simplified, header);
+      Toolbox::SimplifyTags(simplified, header, DicomToJsonFormat_Human);
       call.GetOutput().AnswerJson(simplified);
     }
     else
--- a/OrthancServer/Search/HierarchicalMatcher.cpp	Wed Dec 02 10:05:45 2015 +0100
+++ b/OrthancServer/Search/HierarchicalMatcher.cpp	Mon Dec 07 10:39:23 2015 +0100
@@ -123,12 +123,12 @@
         std::auto_ptr<DicomValue> value(FromDcmtkBridge::ConvertLeafElement
                                         (*element, DicomToJsonFlags_None, encoding));
 
-        if (value->IsBinary() ||
-            value->IsNull())
+        if (value->IsBinary())
         {
           throw OrthancException(ErrorCode_BadRequest);
         }
-        else if (value->GetContent().empty())
+        else if (value->IsNull() ||
+                 value->GetContent().empty())
         {
           // This is an universal matcher
           constraints_[tag] = NULL;
--- a/OrthancServer/ServerContext.cpp	Wed Dec 02 10:05:45 2015 +0100
+++ b/OrthancServer/ServerContext.cpp	Mon Dec 07 10:39:23 2015 +0100
@@ -189,7 +189,7 @@
       resultPublicId = hasher.HashInstance();
 
       Json::Value simplifiedTags;
-      Toolbox::SimplifyTags(simplifiedTags, dicom.GetJson());
+      Toolbox::SimplifyTags(simplifiedTags, dicom.GetJson(), DicomToJsonFormat_Human);
 
       // Test if the instance must be filtered out
       bool accepted = true;
--- a/OrthancServer/ServerEnumerations.h	Wed Dec 02 10:05:45 2015 +0100
+++ b/OrthancServer/ServerEnumerations.h	Mon Dec 07 10:39:23 2015 +0100
@@ -109,7 +109,7 @@
   {
     DicomToJsonFormat_Full,
     DicomToJsonFormat_Short,
-    DicomToJsonFormat_Simple
+    DicomToJsonFormat_Human
   };
 
   enum DicomToJsonFlags
--- a/OrthancServer/ServerToolbox.cpp	Wed Dec 02 10:05:45 2015 +0100
+++ b/OrthancServer/ServerToolbox.cpp	Mon Dec 07 10:39:23 2015 +0100
@@ -47,7 +47,8 @@
   namespace Toolbox
   {
     void SimplifyTags(Json::Value& target,
-                      const Json::Value& source)
+                      const Json::Value& source,
+                      DicomToJsonFormat format)
     {
       assert(source.isObject());
 
@@ -57,9 +58,23 @@
       for (size_t i = 0; i < members.size(); i++)
       {
         const Json::Value& v = source[members[i]];
-        const std::string& name = v["Name"].asString();
         const std::string& type = v["Type"].asString();
 
+        std::string name;
+        switch (format)
+        {
+          case DicomToJsonFormat_Human:
+            name = v["Name"].asString();
+            break;
+
+          case DicomToJsonFormat_Short:
+            name = members[i];
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_ParameterOutOfRange);
+        }
+
         if (type == "String")
         {
           target[name] = v["Value"].asString();
@@ -78,7 +93,7 @@
           for (Json::Value::ArrayIndex i = 0; i < array.size(); i++)
           {
             Json::Value c;
-            SimplifyTags(c, array[i]);
+            SimplifyTags(c, array[i], format);
             children.append(c);
           }
 
@@ -307,6 +322,8 @@
             tmp != level ||
             !FindOneChildInstance(instance, database, resource, level))
         {
+          LOG(ERROR) << "Cannot find an instance for " << EnumerationToString(level) 
+                     << " with identifier " << *it;
           throw OrthancException(ErrorCode_InternalError);
         }
 
@@ -314,23 +331,33 @@
         FileInfo attachment;
         if (!database.LookupAttachment(attachment, instance, FileContentType_Dicom))
         {
+          LOG(ERROR) << "Cannot retrieve the DICOM file associated with instance " << database.GetPublicId(instance);
           throw OrthancException(ErrorCode_InternalError);
         }
 
-        // Read and parse the content of the DICOM file
-        StorageAccessor accessor(storageArea);
+        try
+        {
+          // Read and parse the content of the DICOM file
+          StorageAccessor accessor(storageArea);
 
-        std::string content;
-        accessor.Read(content, attachment);
+          std::string content;
+          accessor.Read(content, attachment);
+
+          ParsedDicomFile dicom(content);
 
-        ParsedDicomFile dicom(content);
+          // Update the tags of this resource
+          DicomMap dicomSummary;
+          dicom.Convert(dicomSummary);
 
-        // Update the tags of this resource
-        DicomMap dicomSummary;
-        dicom.Convert(dicomSummary);
-
-        database.ClearMainDicomTags(resource);
-        Toolbox::SetMainDicomTags(database, resource, level, dicomSummary);
+          database.ClearMainDicomTags(resource);
+          Toolbox::SetMainDicomTags(database, resource, level, dicomSummary);
+        }
+        catch (OrthancException&)
+        {
+          LOG(ERROR) << "Cannot decode the DICOM file with UUID " << attachment.GetUuid()
+                     << " associated with instance " << database.GetPublicId(instance);
+          throw;
+        }
       }
     }
   }
--- a/OrthancServer/ServerToolbox.h	Wed Dec 02 10:05:45 2015 +0100
+++ b/OrthancServer/ServerToolbox.h	Mon Dec 07 10:39:23 2015 +0100
@@ -42,7 +42,8 @@
   namespace Toolbox
   {
     void SimplifyTags(Json::Value& target,
-                      const Json::Value& source);
+                      const Json::Value& source,
+                      DicomToJsonFormat format);
 
     void LogMissingRequiredTag(const DicomMap& summary);
 
--- a/OrthancServer/SliceOrdering.cpp	Wed Dec 02 10:05:45 2015 +0100
+++ b/OrthancServer/SliceOrdering.cpp	Mon Dec 07 10:39:23 2015 +0100
@@ -320,7 +320,8 @@
       if (instances_[i - 1]->GetIndexInSeries() == instances_[i]->GetIndexInSeries())
       {
         // The current "IndexInSeries" occurs 2 times: Not a proper ordering
-        return false;
+        LOG(WARNING) << "This series contains 2 slices with the same index, trying to display it anyway";
+        break;
       }
     }
 
@@ -398,6 +399,8 @@
 
     result["Dicom"] = tmp;
 
+    Json::Value slicesShort = Json::arrayValue;
+
     tmp.clear();
     for (size_t i = 0; i < GetInstancesCount(); i++)
     {
@@ -406,8 +409,16 @@
       {
         tmp.append(base + "/frames/" + boost::lexical_cast<std::string>(j));
       }
+
+      Json::Value tmp2 = Json::arrayValue;
+      tmp2.append(GetInstanceId(i));
+      tmp2.append(0);
+      tmp2.append(GetFramesCount(i));
+      
+      slicesShort.append(tmp2);
     }
 
     result["Slices"] = tmp;
+    result["SlicesShort"] = slicesShort;
   }
 }
--- a/OrthancServer/main.cpp	Wed Dec 02 10:05:45 2015 +0100
+++ b/OrthancServer/main.cpp	Mon Dec 07 10:39:23 2015 +0100
@@ -269,7 +269,7 @@
       }
     }
 
-    return Configuration::GetGlobalBoolParameter(configuration, true);
+    return Configuration::GetGlobalBoolParameter(configuration, false);
   }
 };
 
@@ -835,7 +835,17 @@
 
   LOG(WARNING) << "Upgrading the database from schema version "
                << currentVersion << " to " << ORTHANC_DATABASE_VERSION;
-  database.Upgrade(ORTHANC_DATABASE_VERSION, storageArea);
+
+  try
+  {
+    database.Upgrade(ORTHANC_DATABASE_VERSION, storageArea);
+  }
+  catch (OrthancException&)
+  {
+    LOG(ERROR) << "Unable to run the automated upgrade, please use the replication instructions: "
+               << "https://orthanc.chu.ulg.ac.be/book/users/replication.html";
+    throw;
+  }
     
   // Sanity check
   currentVersion = database.GetDatabaseVersion();
@@ -1099,7 +1109,25 @@
    * Launch Orthanc.
    **/
 
-  LOG(WARNING) << "Orthanc version: " << ORTHANC_VERSION;
+  {
+    std::string version(ORTHANC_VERSION);
+
+    if (std::string(ORTHANC_VERSION) == "mainline")
+    {
+      try
+      {
+        boost::filesystem::path exe(Toolbox::GetPathToExecutable());
+        std::time_t creation = boost::filesystem::last_write_time(exe);
+        boost::posix_time::ptime converted(boost::posix_time::from_time_t(creation));
+        version += " (" + boost::posix_time::to_iso_string(converted) + ")";
+      }
+      catch (...)
+      {
+      }
+    }
+
+    LOG(WARNING) << "Orthanc version: " << version;
+  }
 
   int status = 0;
   try
--- a/Plugins/Engine/OrthancPlugins.cpp	Wed Dec 02 10:05:45 2015 +0100
+++ b/Plugins/Engine/OrthancPlugins.cpp	Mon Dec 07 10:39:23 2015 +0100
@@ -44,6 +44,7 @@
 #include "../../Core/OrthancException.h"
 #include "../../Core/Toolbox.h"
 #include "../../OrthancServer/FromDcmtkBridge.h"
+#include "../../OrthancServer/ToDcmtkBridge.h"
 #include "../../OrthancServer/OrthancInitialization.h"
 #include "../../OrthancServer/ServerContext.h"
 #include "../../OrthancServer/ServerToolbox.h"
@@ -60,6 +61,8 @@
 #include "PluginsEnumerations.h"
 
 #include <boost/regex.hpp> 
+#include <dcmtk/dcmdata/dcdict.h>
+#include <dcmtk/dcmdata/dcdicent.h>
 
 namespace Orthanc
 {
@@ -1187,7 +1190,7 @@
         else
         {
           Json::Value simplified;
-          Toolbox::SimplifyTags(simplified, instance.GetJson());
+          Toolbox::SimplifyTags(simplified, instance.GetJson(), DicomToJsonFormat_Human);
           s = writer.write(simplified);
         }
 
@@ -1590,6 +1593,59 @@
   }
 
 
+
+  namespace
+  {
+    class DictionaryReadLocker
+    {
+    private:
+      const DcmDataDictionary& dictionary_;
+
+    public:
+      DictionaryReadLocker() : dictionary_(dcmDataDict.rdlock())
+      {
+      }
+
+      ~DictionaryReadLocker()
+      {
+        dcmDataDict.unlock();
+      }
+
+      const DcmDataDictionary* operator->()
+      {
+        return &dictionary_;
+      }
+    };
+  }
+
+
+  void OrthancPlugins::ApplyLookupDictionary(const void* parameters)
+  {
+    const _OrthancPluginLookupDictionary& p =
+      *reinterpret_cast<const _OrthancPluginLookupDictionary*>(parameters);
+
+    DicomTag tag(FromDcmtkBridge::ParseTag(p.name));
+    DcmTagKey tag2(tag.GetGroup(), tag.GetElement());
+
+    DictionaryReadLocker locker;
+    const DcmDictEntry* entry = locker->findEntry(tag2, NULL);
+
+    if (entry == NULL)
+    {
+      throw OrthancException(ErrorCode_UnknownDicomTag);
+    }
+    else
+    {
+      p.target->group = entry->getKey().getGroup();
+      p.target->element = entry->getKey().getElement();
+      p.target->vr = Plugins::Convert(entry->getEVR());
+      p.target->minMultiplicity = static_cast<uint32_t>(entry->getVMMin());
+      p.target->maxMultiplicity = (entry->getVMMax() == DcmVariableVM ? 0 : static_cast<uint32_t>(entry->getVMMax()));
+    }
+  }
+
+
+
   bool OrthancPlugins::InvokeService(SharedLibrary& plugin,
                                      _OrthancPluginService service,
                                      const void* parameters)
@@ -2148,6 +2204,10 @@
         ComputeHash(service, parameters);
         return true;
 
+      case _OrthancPluginService_LookupDictionary:
+        ApplyLookupDictionary(parameters);
+        return true;
+
       default:
       {
         // This service is unknown to the Orthanc plugin engine
--- a/Plugins/Engine/OrthancPlugins.h	Wed Dec 02 10:05:45 2015 +0100
+++ b/Plugins/Engine/OrthancPlugins.h	Mon Dec 07 10:39:23 2015 +0100
@@ -150,6 +150,8 @@
     void ApplyCreateImage(_OrthancPluginService service,
                           const void* parameters);
 
+    void ApplyLookupDictionary(const void* parameters);
+
     void ComputeHash(_OrthancPluginService service,
                      const void* parameters);
 
--- a/Plugins/Engine/PluginsEnumerations.cpp	Wed Dec 02 10:05:45 2015 +0100
+++ b/Plugins/Engine/PluginsEnumerations.cpp	Mon Dec 07 10:39:23 2015 +0100
@@ -226,8 +226,8 @@
         case OrthancPluginDicomToJsonFormat_Short:
           return DicomToJsonFormat_Short;
 
-        case OrthancPluginDicomToJsonFormat_Simple:
-          return DicomToJsonFormat_Simple;
+        case OrthancPluginDicomToJsonFormat_Human:
+          return DicomToJsonFormat_Human;
 
         default:
           throw OrthancException(ErrorCode_ParameterOutOfRange);
@@ -394,6 +394,95 @@
           throw OrthancException(ErrorCode_ParameterOutOfRange);
       }
     }
+
+
+    OrthancPluginValueRepresentation Convert(DcmEVR vr)
+    {
+      switch (vr)
+      {
+        case EVR_AE:
+          return OrthancPluginValueRepresentation_AE;
+
+        case EVR_AS:
+          return OrthancPluginValueRepresentation_AS;
+
+        case EVR_AT:
+          return OrthancPluginValueRepresentation_AT;
+
+        case EVR_CS:
+          return OrthancPluginValueRepresentation_CS;
+
+        case EVR_DA:
+          return OrthancPluginValueRepresentation_DA;
+
+        case EVR_DS:
+          return OrthancPluginValueRepresentation_DS;
+
+        case EVR_DT:
+          return OrthancPluginValueRepresentation_DT;
+
+        case EVR_FD:
+          return OrthancPluginValueRepresentation_FD;
+
+        case EVR_FL:
+          return OrthancPluginValueRepresentation_FL;
+
+        case EVR_IS:
+          return OrthancPluginValueRepresentation_IS;
+
+        case EVR_LO:
+          return OrthancPluginValueRepresentation_LO;
+
+        case EVR_LT:
+          return OrthancPluginValueRepresentation_LT;
+
+        case EVR_OB:
+          return OrthancPluginValueRepresentation_OB;
+
+        case EVR_OF:
+          return OrthancPluginValueRepresentation_OF;
+
+        case EVR_OW:
+          return OrthancPluginValueRepresentation_OW;
+
+        case EVR_PN:
+          return OrthancPluginValueRepresentation_PN;
+
+        case EVR_SH:
+          return OrthancPluginValueRepresentation_SH;
+
+        case EVR_SL:
+          return OrthancPluginValueRepresentation_SL;
+
+        case EVR_SQ:
+          return OrthancPluginValueRepresentation_SQ;
+
+        case EVR_SS:
+          return OrthancPluginValueRepresentation_SS;
+
+        case EVR_ST:
+          return OrthancPluginValueRepresentation_ST;
+
+        case EVR_TM:
+          return OrthancPluginValueRepresentation_TM;
+
+        case EVR_UI:
+          return OrthancPluginValueRepresentation_UI;
+
+        case EVR_UL:
+          return OrthancPluginValueRepresentation_UL;
+
+        case EVR_US:
+          return OrthancPluginValueRepresentation_US;
+
+        case EVR_UT:
+          return OrthancPluginValueRepresentation_UT;
+
+        case EVR_UN:
+        default:
+          return OrthancPluginValueRepresentation_UN;  // Unknown
+      }
+    }
 #endif
   }
 }
--- a/Plugins/Engine/PluginsEnumerations.h	Wed Dec 02 10:05:45 2015 +0100
+++ b/Plugins/Engine/PluginsEnumerations.h	Mon Dec 07 10:39:23 2015 +0100
@@ -69,6 +69,8 @@
 
 #if !defined(ORTHANC_ENABLE_DCMTK) || ORTHANC_ENABLE_DCMTK != 0
     DcmEVR Convert(OrthancPluginValueRepresentation vr);
+
+    OrthancPluginValueRepresentation Convert(DcmEVR vr);
 #endif
   }
 }
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Wed Dec 02 10:05:45 2015 +0100
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Mon Dec 07 10:39:23 2015 +0100
@@ -400,6 +400,7 @@
     _OrthancPluginService_CreateDicom = 23,
     _OrthancPluginService_ComputeMd5 = 24,
     _OrthancPluginService_ComputeSha1 = 25,
+    _OrthancPluginService_LookupDictionary = 26,
 
     /* Registration of callbacks */
     _OrthancPluginService_RegisterRestCallback = 1000,
@@ -682,7 +683,7 @@
   {
     OrthancPluginDicomToJsonFormat_Full = 1,    /*!< Full output, with most details */
     OrthancPluginDicomToJsonFormat_Short = 2,   /*!< Tags output as hexadecimal numbers */
-    OrthancPluginDicomToJsonFormat_Simple = 3,  /*!< Human-readable JSON */
+    OrthancPluginDicomToJsonFormat_Human = 3,   /*!< Human-readable JSON */
 
     _OrthancPluginDicomToJsonFormat_INTERNAL = 0x7fffffff
   } OrthancPluginDicomToJsonFormat;
@@ -962,6 +963,20 @@
   } OrthancPluginContext;
 
 
+  
+  /**
+   * @brief An entry in the dictionary of DICOM tags.
+   **/
+  typedef struct
+  {
+    uint16_t                          group;            /*!< The group of the tag */
+    uint16_t                          element;          /*!< The element of the tag */
+    OrthancPluginValueRepresentation  vr;               /*!< The value representation of the tag */
+    uint32_t                          minMultiplicity;  /*!< The minimum multiplicity of the tag */
+    uint32_t                          maxMultiplicity;  /*!< The maximum multiplicity of the tag (0 means arbitrary) */
+  } OrthancPluginDictionaryEntry;
+
+
 
   /**
    * @brief Free a string.
@@ -4627,6 +4642,41 @@
     }
   }
 
+
+
+  typedef struct
+  {
+    OrthancPluginDictionaryEntry* target;
+    const char*                   name;
+  } _OrthancPluginLookupDictionary;
+
+  /**
+   * @brief Get information about the given DICOM tag.
+   *
+   * This functions makes a lookup in the dictionary of DICOM tags
+   * that are known to Orthanc, and returns information about this
+   * tag. The tag can be specified using its human-readable name
+   * (e.g. "PatientName") or a set of two hexadecimal numbers
+   * (e.g. "0010-0020").
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target Where to store the information about the tag.
+   * @param name The name of the DICOM tag.
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginLookupDictionary(
+    OrthancPluginContext*          context,
+    OrthancPluginDictionaryEntry*  target,
+    const char*                    name)
+  {
+    _OrthancPluginLookupDictionary params;
+    params.target = target;
+    params.name = name;
+    return context->InvokeService(context, _OrthancPluginService_LookupDictionary, &params);
+  }
+
+
 #ifdef  __cplusplus
 }
 #endif
--- a/Plugins/Samples/Basic/Plugin.c	Wed Dec 02 10:05:45 2015 +0100
+++ b/Plugins/Samples/Basic/Plugin.c	Mon Dec 07 10:39:23 2015 +0100
@@ -327,6 +327,7 @@
   OrthancPluginMemoryBuffer tmp;
   char info[1024], *s;
   int counter, i;
+  OrthancPluginDictionaryEntry entry;
 
   context = c;
   OrthancPluginLogWarning(context, "Sample plugin is initializing");
@@ -407,6 +408,9 @@
   OrthancPluginRegisterDictionaryTag(context, 0x0014, 0x1020, OrthancPluginValueRepresentation_DA,
                                      "ValidationExpiryDate", 1, 1);
 
+  OrthancPluginLookupDictionary(context, &entry, "ValidationExpiryDate");
+  OrthancPluginLookupDictionary(context, &entry, "0010-0010");
+
   return 0;
 }
 
--- a/Resources/Configuration.json	Wed Dec 02 10:05:45 2015 +0100
+++ b/Resources/Configuration.json	Mon Dec 07 10:39:23 2015 +0100
@@ -104,7 +104,7 @@
 
   // Whether Orthanc accepts to act as C-Store SCP for unknown storage
   // SOP classes (aka. "promiscuous mode")
-  "UnknownSopClassAccepted"            : true,
+  "UnknownSopClassAccepted"            : false,
 
 
 
--- a/UnitTestsSources/FromDcmtkTests.cpp	Wed Dec 02 10:05:45 2015 +0100
+++ b/UnitTestsSources/FromDcmtkTests.cpp	Mon Dec 07 10:39:23 2015 +0100
@@ -395,7 +395,7 @@
       FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Full, DicomToJsonFlags_Default, 0, Encoding_Ascii);
 
       Json::Value c;
-      Toolbox::SimplifyTags(c, b);
+      Toolbox::SimplifyTags(c, b, DicomToJsonFormat_Human);
 
       a[1]["PatientName"] = "Hello2";  // To remove the Data URI Scheme encoding
       ASSERT_EQ(0, c["ReferencedStudySequence"].compare(a));
@@ -474,7 +474,7 @@
     f.ToJson(b, DicomToJsonFormat_Full, DicomToJsonFlags_Default, 0);
 
     Json::Value c;
-    Toolbox::SimplifyTags(c, b);
+    Toolbox::SimplifyTags(c, b, DicomToJsonFormat_Human);
 
     ASSERT_EQ(0, c["ReferencedPatientSequence"].compare(a));
     ASSERT_NE(0, c["ReferencedStudySequence"].compare(a));  // Because Data URI Scheme decoding was enabled
@@ -516,7 +516,7 @@
       f.Replace(DICOM_TAG_PATIENT_NAME, s, false);
 
       Json::Value v;
-      f.ToJson(v, DicomToJsonFormat_Simple, DicomToJsonFlags_Default, 0);
+      f.ToJson(v, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0);
       ASSERT_EQ(v["PatientName"].asString(), std::string(testEncodingsExpected[i]));
     }
   }
@@ -709,7 +709,7 @@
       (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_GenerateIdentifiers)));
 
     Json::Value vv;
-    dicom->ToJson(vv, DicomToJsonFormat_Simple, toJsonFlags, 0);
+    dicom->ToJson(vv, DicomToJsonFormat_Human, toJsonFlags, 0);
 
     ASSERT_EQ(vv["SOPClassUID"].asString(), sopClassUid);
     ASSERT_EQ(vv["MediaStorageSOPClassUID"].asString(), sopClassUid);
@@ -725,7 +725,7 @@
       (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_GenerateIdentifiers)));
 
     Json::Value vv;
-    dicom->ToJson(vv, DicomToJsonFormat_Simple, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePixelData), 0);
+    dicom->ToJson(vv, DicomToJsonFormat_Human, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePixelData), 0);
 
     std::string mime, content;
     Toolbox::DecodeDataUriScheme(mime, content, vv["PixelData"].asString());