changeset 1668:de1413733c97 db-changes

reconstructing main dicom tags
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 30 Sep 2015 17:18:39 +0200
parents 9e875db36aef
children a412ad57f0f9
files OrthancServer/DatabaseWrapper.cpp OrthancServer/DatabaseWrapper.h OrthancServer/IDatabaseWrapper.h OrthancServer/Internals/StoreScp.cpp OrthancServer/OrthancRestApi/OrthancRestResources.cpp OrthancServer/OrthancRestApi/OrthancRestSystem.cpp OrthancServer/ParsedDicomFile.cpp OrthancServer/PrepareDatabase.sql OrthancServer/ServerContext.cpp OrthancServer/ServerEnumerations.h OrthancServer/ServerIndex.cpp OrthancServer/ServerIndex.h OrthancServer/ServerToolbox.cpp OrthancServer/ServerToolbox.h OrthancServer/main.cpp Plugins/Engine/OrthancPluginDatabase.cpp Plugins/Engine/OrthancPluginDatabase.h Plugins/Engine/OrthancPlugins.cpp Plugins/Include/orthanc/OrthancCDatabasePlugin.h Plugins/Include/orthanc/OrthancCppDatabasePlugin.h Resources/Configuration.json
diffstat 21 files changed, 476 insertions(+), 193 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancServer/DatabaseWrapper.cpp	Wed Sep 30 14:04:53 2015 +0200
+++ b/OrthancServer/DatabaseWrapper.cpp	Wed Sep 30 17:18:39 2015 +0200
@@ -465,6 +465,22 @@
   }
 
 
+  void DatabaseWrapper::ClearMainDicomTags(int64_t id)
+  {
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM DicomIdentifiers WHERE id=?");
+      s.BindInt64(0, id);
+      s.Run();
+    }
+
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM MainDicomTags WHERE id=?");
+      s.BindInt64(0, id);
+      s.Run();
+    }
+  }
+
+
   static void SetMainDicomTagsInternal(SQLite::Statement& s,
                                        int64_t id,
                                        const DicomTag& tag,
@@ -836,28 +852,6 @@
   }
 
 
-  void DatabaseWrapper::ExecuteUpgrade5To6(IStorageArea& storageArea)
-  {
-    printf("ICI\n");
-
-    std::auto_ptr<SQLite::ITransaction> transaction(StartTransaction());
-    transaction->Begin();
-
-    std::list<std::string> studies;
-    GetAllPublicIds(studies, ResourceType_Study);
-
-    for (std::list<std::string>::const_iterator
-           it = studies.begin(); it != studies.end(); it++)
-    {
-      printf("[%s]\n", it->c_str());
-    }
-
-    SetGlobalProperty(GlobalProperty_DatabaseSchemaVersion, "6");
-
-    transaction->Commit();
-  }
-
-
   void DatabaseWrapper::Upgrade(unsigned int targetVersion,
                                 IStorageArea& storageArea)
   {
@@ -893,7 +887,13 @@
     if (version_ == 5)
     {
       LOG(WARNING) << "Upgrading database version from 5 to 6";
-      ExecuteUpgrade5To6(storageArea);
+      // No change in the DB schema, the step from version 5 to 6 only
+      // consists in reconstructing the main DICOM tags information.
+      db_.BeginTransaction();
+      SetGlobalProperty(GlobalProperty_DatabaseSchemaVersion, "6");
+      SetGlobalProperty(GlobalProperty_ReconstructStudiesTags, "1");
+      SetGlobalProperty(GlobalProperty_ReconstructSeriesTags, "1");
+      db_.CommitTransaction();
       version_ = 6;
     }    
   }
--- a/OrthancServer/DatabaseWrapper.h	Wed Sep 30 14:04:53 2015 +0200
+++ b/OrthancServer/DatabaseWrapper.h	Wed Sep 30 17:18:39 2015 +0200
@@ -71,8 +71,6 @@
 
     void ClearTable(const std::string& tableName);
 
-    void ExecuteUpgrade5To6(IStorageArea& storageArea);
-
   public:
     DatabaseWrapper(const std::string& path);
 
@@ -132,6 +130,8 @@
                                   int64_t id,
                                   FileContentType contentType);
 
+    virtual void ClearMainDicomTags(int64_t id);
+
     virtual void SetMainDicomTag(int64_t id,
                                  const DicomTag& tag,
                                  const std::string& value);
--- a/OrthancServer/IDatabaseWrapper.h	Wed Sep 30 14:04:53 2015 +0200
+++ b/OrthancServer/IDatabaseWrapper.h	Wed Sep 30 17:18:39 2015 +0200
@@ -168,6 +168,8 @@
     virtual void SetGlobalProperty(GlobalProperty property,
                                    const std::string& value) = 0;
 
+    virtual void ClearMainDicomTags(int64_t id) = 0;
+
     virtual void SetMainDicomTag(int64_t id,
                                  const DicomTag& tag,
                                  const std::string& value) = 0;
--- a/OrthancServer/Internals/StoreScp.cpp	Wed Sep 30 14:04:53 2015 +0200
+++ b/OrthancServer/Internals/StoreScp.cpp	Wed Sep 30 17:18:39 2015 +0200
@@ -211,7 +211,7 @@
 
                 if (e.GetErrorCode() == ErrorCode_InexistentTag)
                 {
-                  LogMissingRequiredTag(summary);
+                  Toolbox::LogMissingRequiredTag(summary);
                 }
                 else
                 {
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Wed Sep 30 14:04:53 2015 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Wed Sep 30 17:18:39 2015 +0200
@@ -208,7 +208,7 @@
       context.ReadJson(full, publicId);
 
       Json::Value simplified;
-      SimplifyTags(simplified, full);
+      Toolbox::SimplifyTags(simplified, full);
       call.GetOutput().AnswerJson(simplified);
     }
     else
@@ -764,7 +764,7 @@
       if (simplify)
       {
         Json::Value simplified;
-        SimplifyTags(simplified, sharedTags);
+        Toolbox::SimplifyTags(simplified, sharedTags);
         call.GetOutput().AnswerJson(simplified);
       }
       else
@@ -831,7 +831,7 @@
     if (simplify)
     {
       Json::Value simplified;
-      SimplifyTags(simplified, result);
+      Toolbox::SimplifyTags(simplified, result);
       call.GetOutput().AnswerJson(simplified);
     }
     else
@@ -1002,7 +1002,7 @@
       if (simplify)
       {
         Json::Value simplified;
-        SimplifyTags(simplified, full);
+        Toolbox::SimplifyTags(simplified, full);
         result[*it] = simplified;
       }
       else
--- a/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp	Wed Sep 30 14:04:53 2015 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp	Wed Sep 30 17:18:39 2015 +0200
@@ -53,9 +53,7 @@
   {
     Json::Value result = Json::objectValue;
 
-    std::string dbVersion = OrthancRestApi::GetIndex(call).GetGlobalProperty(GlobalProperty_DatabaseSchemaVersion, "0");
-
-    result["DatabaseVersion"] = boost::lexical_cast<int>(dbVersion);
+    result["DatabaseVersion"] = OrthancRestApi::GetIndex(call).GetDatabaseVersion();
     result["DicomAet"] = Configuration::GetGlobalStringParameter("DicomAet", "ORTHANC");
     result["DicomPort"] = Configuration::GetGlobalIntegerParameter("DicomPort", 4242);
     result["HttpPort"] = Configuration::GetGlobalIntegerParameter("HttpPort", 8042);
--- a/OrthancServer/ParsedDicomFile.cpp	Wed Sep 30 14:04:53 2015 +0200
+++ b/OrthancServer/ParsedDicomFile.cpp	Wed Sep 30 17:18:39 2015 +0200
@@ -1370,7 +1370,7 @@
     {
       Json::Value tmp;
       FromDcmtkBridge::ToJson(tmp, *pimpl_->file_->getDataset());
-      SimplifyTags(target, tmp);
+      Toolbox::SimplifyTags(target, tmp);
     }
     else
     {
--- a/OrthancServer/PrepareDatabase.sql	Wed Sep 30 14:04:53 2015 +0200
+++ b/OrthancServer/PrepareDatabase.sql	Wed Sep 30 17:18:39 2015 +0200
@@ -123,4 +123,4 @@
 
 -- Set the version of the database schema
 -- The "1" corresponds to the "GlobalProperty_DatabaseSchemaVersion" enumeration
-INSERT INTO GlobalProperties VALUES (1, "5");
+INSERT INTO GlobalProperties VALUES (1, "6");
--- a/OrthancServer/ServerContext.cpp	Wed Sep 30 14:04:53 2015 +0200
+++ b/OrthancServer/ServerContext.cpp	Wed Sep 30 17:18:39 2015 +0200
@@ -189,7 +189,7 @@
       resultPublicId = hasher.HashInstance();
 
       Json::Value simplifiedTags;
-      SimplifyTags(simplifiedTags, dicom.GetJson());
+      Toolbox::SimplifyTags(simplifiedTags, dicom.GetJson());
 
       // Test if the instance must be filtered out
       bool accepted = true;
@@ -299,7 +299,7 @@
     {
       if (e.GetErrorCode() == ErrorCode_InexistentTag)
       {
-        LogMissingRequiredTag(dicom.GetSummary());
+        Toolbox::LogMissingRequiredTag(dicom.GetSummary());
       }
 
       throw;
--- a/OrthancServer/ServerEnumerations.h	Wed Sep 30 14:04:53 2015 +0200
+++ b/OrthancServer/ServerEnumerations.h	Wed Sep 30 17:18:39 2015 +0200
@@ -109,9 +109,13 @@
 
   enum GlobalProperty
   {
-    GlobalProperty_DatabaseSchemaVersion = 1,
+    GlobalProperty_DatabaseSchemaVersion = 1,   // Unused in the Orthanc core as of Orthanc 0.9.5
     GlobalProperty_FlushSleep = 2,
-    GlobalProperty_AnonymizationSequence = 3
+    GlobalProperty_AnonymizationSequence = 3,
+    GlobalProperty_ReconstructPatientsTags = 4,
+    GlobalProperty_ReconstructStudiesTags = 5,
+    GlobalProperty_ReconstructSeriesTags = 6,
+    GlobalProperty_ReconstructInstancesTags = 7
   };
 
   enum MetadataType
--- a/OrthancServer/ServerIndex.cpp	Wed Sep 30 14:04:53 2015 +0200
+++ b/OrthancServer/ServerIndex.cpp	Wed Sep 30 17:18:39 2015 +0200
@@ -40,6 +40,7 @@
 #include "ServerIndexChange.h"
 #include "EmbeddedResources.h"
 #include "OrthancInitialization.h"
+#include "ServerToolbox.h"
 #include "../Core/Toolbox.h"
 #include "../Core/Logging.h"
 #include "../Core/Uuid.h"
@@ -490,18 +491,6 @@
 
 
 
-  void ServerIndex::SetMainDicomTags(int64_t resource,
-                                     const DicomMap& tags)
-  {
-    DicomArray flattened(tags);
-    for (size_t i = 0; i < flattened.GetSize(); i++)
-    {
-      const DicomElement& element = flattened.GetElement(i);
-      db_.SetMainDicomTag(resource, element.GetTag(), element.GetValue().AsString());
-    }
-  }
-
-
   int64_t ServerIndex::CreateResource(const std::string& publicId,
                                       ResourceType type)
   {
@@ -638,10 +627,7 @@
 
       // Create the instance
       int64_t instance = CreateResource(hasher.HashInstance(), ResourceType_Instance);
-
-      DicomMap dicom;
-      dicomSummary.ExtractInstanceInformation(dicom);
-      SetMainDicomTags(instance, dicom);
+      Toolbox::SetMainDicomTags(db_, instance, ResourceType_Instance, dicomSummary, true);
 
       // Detect up to which level the patient/study/series/instance
       // hierarchy must be created
@@ -693,24 +679,22 @@
       if (isNewSeries)
       {
         series = CreateResource(hasher.HashSeries(), ResourceType_Series);
-        dicomSummary.ExtractSeriesInformation(dicom);
-        SetMainDicomTags(series, dicom);
+        Toolbox::SetMainDicomTags(db_, series, ResourceType_Series, dicomSummary, true);
       }
 
       // Create the study if needed
       if (isNewStudy)
       {
         study = CreateResource(hasher.HashStudy(), ResourceType_Study);
-        dicomSummary.ExtractStudyInformation(dicom);
-        SetMainDicomTags(study, dicom);
+        Toolbox::SetMainDicomTags(db_, study, ResourceType_Study, dicomSummary, true);
+        Toolbox::SetMainDicomTags(db_, study, ResourceType_Patient, dicomSummary, false);  // New in version 0.9.5 (db v6)
       }
 
       // Create the patient if needed
       if (isNewPatient)
       {
         patient = CreateResource(hasher.HashPatient(), ResourceType_Patient);
-        dicomSummary.ExtractPatientInformation(dicom);
-        SetMainDicomTags(patient, dicom);
+        Toolbox::SetMainDicomTags(db_, patient, ResourceType_Patient, dicomSummary, true);
       }
 
       // Create the parent-to-child links
@@ -892,7 +876,8 @@
 
 
   void ServerIndex::MainDicomTagsToJson(Json::Value& target,
-                                        int64_t resourceId)
+                                        int64_t resourceId,
+                                        ResourceType resourceType)
   {
     DicomMap tags;
     db_.GetMainDicomTags(tags, resourceId);
@@ -1033,7 +1018,7 @@
 
     // Record the remaining information
     result["ID"] = publicId;
-    MainDicomTagsToJson(result, id);
+    MainDicomTagsToJson(result, id, type);
 
     std::string tmp;
 
@@ -2108,4 +2093,10 @@
     return db_.LookupResource(id, type, publicId);
   }
 
+
+  unsigned int ServerIndex::GetDatabaseVersion()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    return db_.GetDatabaseVersion();
+  }
 }
--- a/OrthancServer/ServerIndex.h	Wed Sep 30 14:04:53 2015 +0200
+++ b/OrthancServer/ServerIndex.h	Wed Sep 30 17:18:39 2015 +0200
@@ -76,7 +76,8 @@
     static void UnstableResourcesMonitorThread(ServerIndex* that);
 
     void MainDicomTagsToJson(Json::Value& result,
-                             int64_t resourceId);
+                             int64_t resourceId,
+                             ResourceType resourceType);
 
     SeriesStatus GetSeriesStatus(int64_t id);
 
@@ -110,9 +111,6 @@
 
     uint64_t IncrementGlobalSequenceInternal(GlobalProperty property);
 
-    void SetMainDicomTags(int64_t resource,
-                          const DicomMap& tags);
-
     int64_t CreateResource(const std::string& publicId,
                            ResourceType type);
 
@@ -267,5 +265,7 @@
 
     bool LookupResourceType(ResourceType& type,
                             const std::string& publicId);
+
+    unsigned int GetDatabaseVersion();
   };
 }
--- a/OrthancServer/ServerToolbox.cpp	Wed Sep 30 14:04:53 2015 +0200
+++ b/OrthancServer/ServerToolbox.cpp	Wed Sep 30 17:18:39 2015 +0200
@@ -35,120 +35,267 @@
 
 #include "../Core/Logging.h"
 #include "../Core/OrthancException.h"
+#include "../Core/DicomFormat/DicomArray.h"
+#include "ParsedDicomFile.h"
 
 #include <cassert>
 
 namespace Orthanc
 {
-  void SimplifyTags(Json::Value& target,
-                    const Json::Value& source)
+  namespace Toolbox
   {
-    assert(source.isObject());
+    void SimplifyTags(Json::Value& target,
+                      const Json::Value& source)
+    {
+      assert(source.isObject());
 
-    target = Json::objectValue;
-    Json::Value::Members members = source.getMemberNames();
+      target = Json::objectValue;
+      Json::Value::Members members = source.getMemberNames();
+
+      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();
 
-    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();
+        if (type == "String")
+        {
+          target[name] = v["Value"].asString();
+        }
+        else if (type == "TooLong" ||
+                 type == "Null")
+        {
+          target[name] = Json::nullValue;
+        }
+        else if (type == "Sequence")
+        {
+          const Json::Value& array = v["Value"];
+          assert(array.isArray());
 
-      if (type == "String")
-      {
-        target[name] = v["Value"].asString();
+          Json::Value children = Json::arrayValue;
+          for (Json::Value::ArrayIndex i = 0; i < array.size(); i++)
+          {
+            Json::Value c;
+            SimplifyTags(c, array[i]);
+            children.append(c);
+          }
+
+          target[name] = children;
+        }
+        else
+        {
+          assert(0);
+        }
       }
-      else if (type == "TooLong" ||
-               type == "Null")
+    }
+
+
+    void LogMissingRequiredTag(const DicomMap& summary)
+    {
+      std::string s, t;
+
+      if (summary.HasTag(DICOM_TAG_PATIENT_ID))
       {
-        target[name] = Json::nullValue;
+        if (t.size() > 0)
+          t += ", ";
+        t += "PatientID=" + summary.GetValue(DICOM_TAG_PATIENT_ID).AsString();
       }
-      else if (type == "Sequence")
+      else
       {
-        const Json::Value& array = v["Value"];
-        assert(array.isArray());
+        if (s.size() > 0)
+          s += ", ";
+        s += "PatientID";
+      }
 
-        Json::Value children = Json::arrayValue;
-        for (Json::Value::ArrayIndex i = 0; i < array.size(); i++)
-        {
-          Json::Value c;
-          SimplifyTags(c, array[i]);
-          children.append(c);
-        }
+      if (summary.HasTag(DICOM_TAG_STUDY_INSTANCE_UID))
+      {
+        if (t.size() > 0)
+          t += ", ";
+        t += "StudyInstanceUID=" + summary.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString();
+      }
+      else
+      {
+        if (s.size() > 0)
+          s += ", ";
+        s += "StudyInstanceUID";
+      }
 
-        target[name] = children;
+      if (summary.HasTag(DICOM_TAG_SERIES_INSTANCE_UID))
+      {
+        if (t.size() > 0)
+          t += ", ";
+        t += "SeriesInstanceUID=" + summary.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString();
+      }
+      else
+      {
+        if (s.size() > 0)
+          s += ", ";
+        s += "SeriesInstanceUID";
+      }
+
+      if (summary.HasTag(DICOM_TAG_SOP_INSTANCE_UID))
+      {
+        if (t.size() > 0)
+          t += ", ";
+        t += "SOPInstanceUID=" + summary.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString();
+      }
+      else
+      {
+        if (s.size() > 0)
+          s += ", ";
+        s += "SOPInstanceUID";
+      }
+
+      if (t.size() == 0)
+      {
+        LOG(ERROR) << "Store has failed because all the required tags (" << s << ") are missing (is it a DICOMDIR file?)";
       }
       else
       {
-        assert(0);
+        LOG(ERROR) << "Store has failed because required tags (" << s << ") are missing for the following instance: " << t;
+      }
+    }
+
+
+    void SetMainDicomTags(IDatabaseWrapper& database,
+                          int64_t resource,
+                          ResourceType level,
+                          const DicomMap& dicomSummary,
+                          bool includeIdentifiers)
+    {
+      // WARNING: The database should be locked with a transaction!
+
+      DicomMap tags;
+
+      switch (level)
+      {
+        case ResourceType_Patient:
+          dicomSummary.ExtractPatientInformation(tags);
+          break;
+
+        case ResourceType_Study:
+          dicomSummary.ExtractStudyInformation(tags);
+          break;
+
+        case ResourceType_Series:
+          dicomSummary.ExtractSeriesInformation(tags);
+          break;
+
+        case ResourceType_Instance:
+          dicomSummary.ExtractInstanceInformation(tags);
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+
+      DicomArray flattened(tags);
+      for (size_t i = 0; i < flattened.GetSize(); i++)
+      {
+        const DicomElement& element = flattened.GetElement(i);
+
+        if (includeIdentifiers ||
+            !element.GetTag().IsIdentifier())
+        {
+          database.SetMainDicomTag(resource, element.GetTag(), element.GetValue().AsString());
+        }
+      }
+    }
+
+
+    bool FindOneChildInstance(int64_t& result,
+                              IDatabaseWrapper& database,
+                              int64_t resource,
+                              ResourceType type)
+    {
+      for (;;)
+      {
+        if (type == ResourceType_Instance)
+        {
+          result = resource;
+          return true;
+        }
+
+        std::list<int64_t> children;
+        database.GetChildrenInternalId(children, resource);
+        if (children.empty())
+        {
+          return false;
+        }
+
+        resource = children.front();
+        type = GetChildResourceType(type);    
+      }
+    }
+
+
+    void ReconstructMainDicomTags(IDatabaseWrapper& database,
+                                  IStorageArea& storageArea,
+                                  ResourceType level)
+    {
+      // WARNING: The database should be locked with a transaction!
+
+      std::list<std::string> resources;
+      database.GetAllPublicIds(resources, level);
+
+      for (std::list<std::string>::const_iterator
+             it = resources.begin(); it != resources.end(); it++)
+      {
+        // Locate the resource and one of its child instances
+        int64_t resource, instance;
+        ResourceType tmp;
+
+        if (!database.LookupResource(resource, tmp, *it) ||
+            tmp != level ||
+            !FindOneChildInstance(instance, database, resource, level))
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+
+        // Get the DICOM file attached to some instances in the resource
+        FileInfo attachment;
+        if (!database.LookupAttachment(attachment, instance, FileContentType_Dicom))
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+
+        // Read and parse the content of the DICOM file
+        std::string content;
+        storageArea.Read(content, attachment.GetUuid(), FileContentType_Dicom);
+
+        ParsedDicomFile dicom(content);
+
+        // Update the tags of this resource
+        DicomMap dicomSummary;
+        dicom.Convert(dicomSummary);
+
+        database.ClearMainDicomTags(resource);
+
+        switch (level)
+        {
+          case ResourceType_Patient:
+            Toolbox::SetMainDicomTags(database, resource, ResourceType_Patient, dicomSummary, true);
+            break;
+
+          case ResourceType_Study:
+            Toolbox::SetMainDicomTags(database, resource, ResourceType_Study, dicomSummary, true);
+
+            // Duplicate the patient tags at the study level
+            Toolbox::SetMainDicomTags(database, resource, ResourceType_Patient, dicomSummary, false);
+            break;
+
+          case ResourceType_Series:
+            Toolbox::SetMainDicomTags(database, resource, ResourceType_Series, dicomSummary, true);
+            break;
+
+          case ResourceType_Instance:
+            Toolbox::SetMainDicomTags(database, resource, ResourceType_Instance, dicomSummary, true);
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_InternalError);
+        }
       }
     }
   }
-
-
-  void LogMissingRequiredTag(const DicomMap& summary)
-  {
-    std::string s, t;
-
-    if (summary.HasTag(DICOM_TAG_PATIENT_ID))
-    {
-      if (t.size() > 0)
-        t += ", ";
-      t += "PatientID=" + summary.GetValue(DICOM_TAG_PATIENT_ID).AsString();
-    }
-    else
-    {
-      if (s.size() > 0)
-        s += ", ";
-      s += "PatientID";
-    }
-
-    if (summary.HasTag(DICOM_TAG_STUDY_INSTANCE_UID))
-    {
-      if (t.size() > 0)
-        t += ", ";
-      t += "StudyInstanceUID=" + summary.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString();
-    }
-    else
-    {
-      if (s.size() > 0)
-        s += ", ";
-      s += "StudyInstanceUID";
-    }
-
-    if (summary.HasTag(DICOM_TAG_SERIES_INSTANCE_UID))
-    {
-      if (t.size() > 0)
-        t += ", ";
-      t += "SeriesInstanceUID=" + summary.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString();
-    }
-    else
-    {
-      if (s.size() > 0)
-        s += ", ";
-      s += "SeriesInstanceUID";
-    }
-
-    if (summary.HasTag(DICOM_TAG_SOP_INSTANCE_UID))
-    {
-      if (t.size() > 0)
-        t += ", ";
-      t += "SOPInstanceUID=" + summary.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString();
-    }
-    else
-    {
-      if (s.size() > 0)
-        s += ", ";
-      s += "SOPInstanceUID";
-    }
-
-    if (t.size() == 0)
-    {
-      LOG(ERROR) << "Store has failed because all the required tags (" << s << ") are missing (is it a DICOMDIR file?)";
-    }
-    else
-    {
-      LOG(ERROR) << "Store has failed because required tags (" << s << ") are missing for the following instance: " << t;
-    }
-  }
 }
--- a/OrthancServer/ServerToolbox.h	Wed Sep 30 14:04:53 2015 +0200
+++ b/OrthancServer/ServerToolbox.h	Wed Sep 30 17:18:39 2015 +0200
@@ -33,13 +33,32 @@
 #pragma once
 
 #include "../Core/DicomFormat/DicomMap.h"
+#include "IDatabaseWrapper.h"
 
 #include <json/json.h>
 
 namespace Orthanc
 {
-  void SimplifyTags(Json::Value& target,
-                    const Json::Value& source);
+  namespace Toolbox
+  {
+    void SimplifyTags(Json::Value& target,
+                      const Json::Value& source);
+
+    void LogMissingRequiredTag(const DicomMap& summary);
 
-  void LogMissingRequiredTag(const DicomMap& summary);
+    void SetMainDicomTags(IDatabaseWrapper& database,
+                          int64_t resource,
+                          ResourceType level,
+                          const DicomMap& dicomSummary,
+                          bool includeIdentifiers);
+
+    bool FindOneChildInstance(int64_t& result,
+                              IDatabaseWrapper& database,
+                              int64_t resource,
+                              ResourceType type);
+
+    void ReconstructMainDicomTags(IDatabaseWrapper& database,
+                                  IStorageArea& storageArea,
+                                  ResourceType level);
+  }
 }
--- a/OrthancServer/main.cpp	Wed Sep 30 14:04:53 2015 +0200
+++ b/OrthancServer/main.cpp	Wed Sep 30 17:18:39 2015 +0200
@@ -613,43 +613,114 @@
 }
 
 
+
+
+static bool ReconstructLevel(IDatabaseWrapper& database,
+                             IStorageArea& storageArea,
+                             bool allowDatabaseUpgrade,
+                             ResourceType level)
+{
+  GlobalProperty property;
+  const char* plural = NULL;
+
+  switch (level)
+  {
+    case ResourceType_Patient:
+      plural = "patients";
+      property = GlobalProperty_ReconstructPatientsTags;
+      break;
+
+    case ResourceType_Study:
+      plural = "studies";
+      property = GlobalProperty_ReconstructStudiesTags;
+      break;
+
+    case ResourceType_Series:
+      plural = "series";
+      property = GlobalProperty_ReconstructSeriesTags;
+      break;
+
+    case ResourceType_Instance:
+      plural = "instances";
+      property = GlobalProperty_ReconstructInstancesTags;
+      break;
+
+    default:
+      throw OrthancException(ErrorCode_InternalError);
+  }
+
+  std::string tmp;
+  if (database.LookupGlobalProperty(tmp, property) && tmp != "0")
+  {
+    if (!allowDatabaseUpgrade)
+    {
+      LOG(ERROR) << "The main DICOM tags of all the " << plural
+                 << " must be reconstructed: "
+                 << "Please run Orthanc with the \"--upgrade\" command-line option";
+      return false;
+    }
+
+    std::auto_ptr<SQLite::ITransaction> transaction(database.StartTransaction());
+    transaction->Begin();
+
+    LOG(WARNING) << "Upgrade: Reconstructing the main DICOM tags of all the " << plural << "...";
+    Toolbox::ReconstructMainDicomTags(database, storageArea, level);
+
+    database.SetGlobalProperty(property, "0");
+
+    transaction->Commit();
+  }
+
+  return true;
+}
+
+
 static bool UpgradeDatabase(IDatabaseWrapper& database,
                             IStorageArea& storageArea,
                             bool allowDatabaseUpgrade)
 {
-  // Upgrade the database, if needed
+  // Upgrade the schema of the database, if needed
   unsigned int currentVersion = database.GetDatabaseVersion();
-  if (currentVersion == ORTHANC_DATABASE_VERSION)
+  if (currentVersion != ORTHANC_DATABASE_VERSION)
   {
-    return true;
+    if (currentVersion > ORTHANC_DATABASE_VERSION)
+    {
+      LOG(ERROR) << "The version of the database schema (" << currentVersion
+                 << ") is too recent for this version of Orthanc. Please upgrade Orthanc.";
+      return false;
+    }
+
+    if (!allowDatabaseUpgrade)
+    {
+      LOG(ERROR) << "The database schema must be upgraded from version "
+                 << currentVersion << " to " << ORTHANC_DATABASE_VERSION 
+                 << ": Please run Orthanc with the \"--upgrade\" command-line option";
+      return false;
+    }
+
+    LOG(WARNING) << "Upgrading the database from schema version "
+                 << currentVersion << " to " << ORTHANC_DATABASE_VERSION;
+    database.Upgrade(ORTHANC_DATABASE_VERSION, storageArea);
+    
+    // Sanity check
+    currentVersion = database.GetDatabaseVersion();
+    if (ORTHANC_DATABASE_VERSION != currentVersion)
+    {
+      LOG(ERROR) << "The database schema was not properly upgraded, it is still at version " << currentVersion;
+      throw OrthancException(ErrorCode_InternalError);
+    }
   }
 
-  if (currentVersion > ORTHANC_DATABASE_VERSION)
+
+  // Reconstruct the main DICOM tags at each level, if needed
+  if (!ReconstructLevel(database, storageArea, allowDatabaseUpgrade, ResourceType_Patient) ||
+      !ReconstructLevel(database, storageArea, allowDatabaseUpgrade, ResourceType_Study) ||
+      !ReconstructLevel(database, storageArea, allowDatabaseUpgrade, ResourceType_Series) ||
+      !ReconstructLevel(database, storageArea, allowDatabaseUpgrade, ResourceType_Instance))
   {
-    LOG(ERROR) << "The version of the database (" << currentVersion
-               << ") is too recent for this version of Orthanc. Please upgrade Orthanc.";
     return false;
   }
 
-  if (!allowDatabaseUpgrade)
-  {
-    LOG(ERROR) << "The database must be upgraded from version "
-               << currentVersion << " to " << ORTHANC_DATABASE_VERSION 
-               << ": Please run Orthanc with the \"--upgrade\" command-line option";
-    return false;
-  }
-
-  LOG(WARNING) << "Upgrading the database from version "
-               << currentVersion << " to " << ORTHANC_DATABASE_VERSION;
-  database.Upgrade(ORTHANC_DATABASE_VERSION, storageArea);
-    
-  // Sanity check
-  currentVersion = database.GetDatabaseVersion();
-  if (ORTHANC_DATABASE_VERSION != currentVersion)
-  {
-    LOG(ERROR) << "The database was not properly updated, it is still at version " << currentVersion;
-    throw OrthancException(ErrorCode_InternalError);
-  }
 
   return true;
 }
--- a/Plugins/Engine/OrthancPluginDatabase.cpp	Wed Sep 30 14:04:53 2015 +0200
+++ b/Plugins/Engine/OrthancPluginDatabase.cpp	Wed Sep 30 17:18:39 2015 +0200
@@ -985,6 +985,24 @@
   }
 
 
+  void OrthancPluginDatabase::ClearMainDicomTags(int64_t id)
+  {
+    if (extensions_.clearMainDicomTags != NULL)
+    {
+      LOG(ERROR) << "Your custom index plugin does not implement the ClearMainDicomTags() extension";
+      throw OrthancException(ErrorCode_DatabasePlugin);
+    }
+
+    OrthancPluginErrorCode error = extensions_.clearMainDicomTags(payload_, id);
+    
+    if (error != OrthancPluginErrorCode_Success)
+    {
+      errorDictionary_.LogError(error, true);
+      throw OrthancException(static_cast<ErrorCode>(error));
+    }
+  }
+
+
   void OrthancPluginDatabase::SetMainDicomTag(int64_t id,
                                               const DicomTag& tag,
                                               const std::string& value)
--- a/Plugins/Engine/OrthancPluginDatabase.h	Wed Sep 30 14:04:53 2015 +0200
+++ b/Plugins/Engine/OrthancPluginDatabase.h	Wed Sep 30 17:18:39 2015 +0200
@@ -217,6 +217,8 @@
     virtual void SetGlobalProperty(GlobalProperty property,
                                    const std::string& value);
 
+    virtual void ClearMainDicomTags(int64_t id);
+
     virtual void SetMainDicomTag(int64_t id,
                                  const DicomTag& tag,
                                  const std::string& value);
--- a/Plugins/Engine/OrthancPlugins.cpp	Wed Sep 30 14:04:53 2015 +0200
+++ b/Plugins/Engine/OrthancPlugins.cpp	Wed Sep 30 17:18:39 2015 +0200
@@ -1006,7 +1006,7 @@
         else
         {
           Json::Value simplified;
-          SimplifyTags(simplified, instance.GetJson());
+          Toolbox::SimplifyTags(simplified, instance.GetJson());
           s = writer.write(simplified);
         }
 
--- a/Plugins/Include/orthanc/OrthancCDatabasePlugin.h	Wed Sep 30 14:04:53 2015 +0200
+++ b/Plugins/Include/orthanc/OrthancCDatabasePlugin.h	Wed Sep 30 17:18:39 2015 +0200
@@ -655,7 +655,12 @@
       void* payload,
       uint32_t targetVersion,
       OrthancPluginStorageArea* storageArea);
-  } OrthancPluginDatabaseExtensions;
+ 
+    OrthancPluginErrorCode  (*clearMainDicomTags) (
+      /* inputs */
+      void* payload,
+      int64_t id);
+   } OrthancPluginDatabaseExtensions;
 
 /*<! @endcond */
 
--- a/Plugins/Include/orthanc/OrthancCppDatabasePlugin.h	Wed Sep 30 14:04:53 2015 +0200
+++ b/Plugins/Include/orthanc/OrthancCppDatabasePlugin.h	Wed Sep 30 17:18:39 2015 +0200
@@ -458,6 +458,8 @@
 
     virtual void UpgradeDatabase(uint32_t  targetVersion,
                                  OrthancPluginStorageArea* storageArea) = 0;
+
+    virtual void ClearMainDicomTags(int64_t internalId) = 0;
   };
 
 
@@ -1780,6 +1782,28 @@
     }
 
     
+    static OrthancPluginErrorCode ClearMainDicomTags(void* payload,
+                                                     int64_t internalId)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      
+      try
+      {
+        backend->ClearMainDicomTags(internalId);
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return OrthancPluginErrorCode_DatabasePlugin;
+      }
+      catch (DatabaseException& e)
+      {
+        return e.GetErrorCode();
+      }
+    }
+
+    
   public:
     /**
      * Register a custom database back-end written in C++.
@@ -1847,6 +1871,7 @@
       extensions.getAllPublicIdsWithLimit = GetAllPublicIdsWithLimit;
       extensions.getDatabaseVersion = GetDatabaseVersion;
       extensions.upgradeDatabase = UpgradeDatabase;
+      extensions.clearMainDicomTags = ClearMainDicomTags;
 
       OrthancPluginDatabaseContext* database = OrthancPluginRegisterDatabaseBackendV2(context, &params, &extensions, &backend);
       if (!context)
--- a/Resources/Configuration.json	Wed Sep 30 14:04:53 2015 +0200
+++ b/Resources/Configuration.json	Wed Sep 30 17:18:39 2015 +0200
@@ -243,7 +243,8 @@
   "KeepAlive" : false,
 
   // If this option is set to "false", Orthanc will run in index-only
-  // mode. The DICOM files will not be stored on the drive.
+  // mode. The DICOM files will not be stored on the drive. Note that
+  // this option might prevent the upgrade to newer versions of Orthanc.
   "StoreDicom" : true,
 
   // DICOM associations are kept open as long as new DICOM commands