changeset 4944:f377d5643538 more-tags

new Warnings configuration + InstanceAvailability tag
author Alain Mazy <am@osimis.io>
date Thu, 17 Mar 2022 17:03:59 +0100
parents 96a3e81eba90
children 3778a0433dd3
files NEWS OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake OrthancFramework/Sources/DicomFormat/DicomMap.cpp OrthancFramework/Sources/DicomFormat/DicomTag.h OrthancFramework/UnitTestsSources/DicomMapTests.cpp OrthancServer/Resources/Configuration.json OrthancServer/Sources/OrthancConfiguration.cpp OrthancServer/Sources/OrthancConfiguration.h OrthancServer/Sources/OrthancInitialization.cpp OrthancServer/Sources/ServerContext.cpp TODO
diffstat 11 files changed, 181 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Wed Mar 16 09:50:33 2022 +0100
+++ b/NEWS	Thu Mar 17 17:03:59 2022 +0100
@@ -2,6 +2,38 @@
 ===============================
 
 
+General
+-------
+
+* New configuration "ExtraMainDicomTags" to store more tags in the Index DB
+  to speed up, e.g, building C-Find, dicom-web or tools/find answers
+* New configuration "Warnings" to enable/disable individual warnings that can
+  be identified by a W0XX prefix in the logs.
+  These warnings have been added:
+  - W001_TagsBeingReadFromStorage
+  - W002_InconsistentDicomTagsInDb
+* C-Find and QIDO-RS can now return the InstanceAvailability tag.  Value is 
+  always "ONLINE"
+
+REST API
+--------
+
+* API version upgraded to 17
+* new options in tools/find:
+  - "RequestedTags" (to use together with "Expand": true) contains a list of tags 
+    that you'll receive in the "RequestedTags" field in the answers.  These tags
+    may be tags from the MainDicomTags in DB, from the DICOM file or 'computed'
+    like ModalitiesInStudy.  Check the new configuration "ExtraMainDicomTags" and
+    "Warnings" to optimize your queries.
+  - "EnableStorageAccessOnSearch": TODO: not implemented yet
+  - "EnableStorageAccessOnAnwers": TODO: not implemented yet
+* new query argument "requestedTags" in all API routes listing resources:
+  - /patients, /patients/../studies, /patients/../series, /patients/../instances
+  - /studies, /studies/../series, /studies/../instances
+  - /series, /series/../instances
+  - /instances
+
+
 
 Documentation
 -------------
--- a/OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake	Wed Mar 16 09:50:33 2022 +0100
+++ b/OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake	Thu Mar 17 17:03:59 2022 +0100
@@ -38,7 +38,7 @@
 # Version of the Orthanc API, can be retrieved from "/system" URI in
 # order to check whether new URI endpoints are available even if using
 # the mainline version of Orthanc
-set(ORTHANC_API_VERSION "16")
+set(ORTHANC_API_VERSION "17")
 
 
 #####################################################################
--- a/OrthancFramework/Sources/DicomFormat/DicomMap.cpp	Wed Mar 16 09:50:33 2022 +0100
+++ b/OrthancFramework/Sources/DicomFormat/DicomMap.cpp	Thu Mar 17 17:03:59 2022 +0100
@@ -634,16 +634,28 @@
             IsMainDicomTag(tag, ResourceType_Instance));
   }
 
+  static bool IsGenericComputedTag(const DicomTag& tag)
+  {
+    return tag == DICOM_TAG_RETRIEVE_URL ||
+      tag == DICOM_TAG_RETRIEVE_AE_TITLE;
+  }
+
   bool DicomMap::IsComputedTag(const DicomTag& tag)
   {
     return (IsComputedTag(tag, ResourceType_Patient) ||
             IsComputedTag(tag, ResourceType_Study) ||
             IsComputedTag(tag, ResourceType_Series) ||
-            IsComputedTag(tag, ResourceType_Instance));
+            IsComputedTag(tag, ResourceType_Instance) ||
+            IsGenericComputedTag(tag));
   }
 
   bool DicomMap::IsComputedTag(const DicomTag& tag, ResourceType level)
   {
+    if (IsGenericComputedTag(tag))
+    {
+      return true;
+    }
+
     switch (level)
     {
       case ResourceType_Patient:
@@ -664,7 +676,9 @@
           tag == DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES
         );
       case ResourceType_Instance:
-        return false;
+        return (
+          tag == DICOM_TAG_INSTANCE_AVAILABILITY
+        );
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
--- a/OrthancFramework/Sources/DicomFormat/DicomTag.h	Wed Mar 16 09:50:33 2022 +0100
+++ b/OrthancFramework/Sources/DicomFormat/DicomTag.h	Thu Mar 17 17:03:59 2022 +0100
@@ -117,6 +117,7 @@
   static const DicomTag DICOM_TAG_QUERY_RETRIEVE_LEVEL(0x0008, 0x0052);
   static const DicomTag DICOM_TAG_MODALITIES_IN_STUDY(0x0008, 0x0061);
   static const DicomTag DICOM_TAG_RETRIEVE_AE_TITLE(0x0008, 0x0054);
+  static const DicomTag DICOM_TAG_INSTANCE_AVAILABILITY(0x0008, 0x0056);
 
   // Tags for images
   static const DicomTag DICOM_TAG_COLUMNS(0x0028, 0x0011);
@@ -229,4 +230,8 @@
   static const DicomTag DICOM_TAG_OFFSET_OF_REFERENCED_LOWER_LEVEL_DIRECTORY_ENTITY(0x0004, 0x1420);
   static const DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID_IN_FILE(0x0004, 0x1511);
   static const DicomTag DICOM_TAG_REFERENCED_FILE_ID(0x0004, 0x1500);
+
+  // Tags for DicomWeb
+  static const Orthanc::DicomTag DICOM_TAG_RETRIEVE_URL(0x0008, 0x1190);
+
 }
--- a/OrthancFramework/UnitTestsSources/DicomMapTests.cpp	Wed Mar 16 09:50:33 2022 +0100
+++ b/OrthancFramework/UnitTestsSources/DicomMapTests.cpp	Thu Mar 17 17:03:59 2022 +0100
@@ -582,6 +582,7 @@
   {
     std::set<DicomTag> tags;
     tags.insert(DICOM_TAG_MODALITIES_IN_STUDY);
+    tags.insert(DICOM_TAG_RETRIEVE_URL);
 
     ASSERT_TRUE(DicomMap::HasOnlyComputedTags(tags));
     ASSERT_TRUE(DicomMap::HasComputedTags(tags, ResourceType_Study));
--- a/OrthancServer/Resources/Configuration.json	Wed Mar 16 09:50:33 2022 +0100
+++ b/OrthancServer/Resources/Configuration.json	Thu Mar 17 17:03:59 2022 +0100
@@ -863,6 +863,7 @@
   // Extra Main Dicom tags that are stored in DB together with all default
   // Main Dicom tags that are already stored (TODO: see book new page). 
   // (new in Orthanc 1.11.0)
+  // Sequences tags are not supported.
   /**
   "ExtraMainDicomTags" : {
     "Instance" : [
@@ -883,11 +884,23 @@
   },
   */
 
-  // Enables/disables a warning notifying you when you try to access a
-  // resource that has been saved with a different version of the 
-  // ExtraMainDicomTags list
+  // Enables/disables warnings in the logs.
+  // "true" enables a warning.  All warnings are enabled by default
   // TODO: see book new page
   // (new in Orthanc 1.11.0)
-  "EnableLogsForInconsistentMainDicomTags": true
+  "Warnings" : {
+    // A "RequestedTags" has been read from storage which is slower than
+    // reading it from DB.
+    // You might want to store this tag in ExtraMainDicomTags to build
+    // the response faster.
+    "W001_TagsBeingReadFromStorage": true,
+    
+    // Retrieving a list of Main dicom tags from a resource that has been
+    // saved with another "ExtraMainDicomTags" configuration which means that
+    // your response might be incomplete/inconsistent.
+    // You should call patients|studies|series|instances/../reconstruct to rebuild
+    // the DB.  TODO: also check for "rebuild DB" plugin
+    "W002_InconsistentDicomTagsInDb": true
+  }
 
 }
--- a/OrthancServer/Sources/OrthancConfiguration.cpp	Wed Mar 16 09:50:33 2022 +0100
+++ b/OrthancServer/Sources/OrthancConfiguration.cpp	Thu Mar 17 17:03:59 2022 +0100
@@ -43,6 +43,7 @@
 static const char* const ORTHANC_PEERS_IN_DB = "OrthancPeersInDatabase";
 static const char* const TEMPORARY_DIRECTORY = "TemporaryDirectory";
 static const char* const DATABASE_SERVER_IDENTIFIER = "DatabaseServerIdentifier";
+static const char* const WARNINGS = "Warnings";
 
 namespace Orthanc
 {
@@ -1055,14 +1056,48 @@
     }
   }
 
-  bool OrthancConfiguration::IsInconsistentDicomTagsLogsEnabled() const
+  void OrthancConfiguration::LoadWarnings()
   {
-    return GetBooleanParameter("EnableLogsForInconsistentMainDicomTags", true);
-  }
+    if (json_.isMember(WARNINGS))
+    {
+      const Json::Value& warnings = json_[WARNINGS];
+      if (!warnings.isObject())
+      {
+        throw OrthancException(ErrorCode_BadFileFormat, std::string(WARNINGS) + " configuration entry is not a Json object");
+      }
+
+      Json::Value::Members members = warnings.getMemberNames();
+
+      for (size_t i = 0; i < members.size(); i++)
+      {
+        const std::string& name = members[i];
+        bool enabled = warnings[name].asBool();
 
-  bool OrthancConfiguration::IsStorageAccessOnFindLogsEnabled() const
-  {
-    return GetBooleanParameter("EnableLogsForStorageAccessOnFind", true);
+        Warnings warning = Warnings_None;
+        if (name == "W001_TagsBeingReadFromStorage")
+        {
+          warning = Warnings_001_TagsBeingReadFromStorage;
+        }
+        else if (name == "W002_InconsistentDicomTagsInDb")
+        {
+          warning = Warnings_002_InconsistentDicomTagsInDb;
+        }
+        else
+        {
+          throw OrthancException(ErrorCode_BadFileFormat, name + " is not recognized as a valid warning name");
+        }
+
+        if (!enabled)
+        {
+          disabledWarnings_.insert(warning);
+        }
+      }
+    }
+    else
+    {
+      disabledWarnings_.clear();
+    }
+
   }
 
 
--- a/OrthancServer/Sources/OrthancConfiguration.h	Wed Mar 16 09:50:33 2022 +0100
+++ b/OrthancServer/Sources/OrthancConfiguration.h	Thu Mar 17 17:03:59 2022 +0100
@@ -31,6 +31,7 @@
 #include <boost/filesystem.hpp>
 #include <boost/thread/shared_mutex.hpp>
 #include <boost/thread/lock_types.hpp>
+#include <set>
 
 class DcmDataset;
 
@@ -42,7 +43,15 @@
   class ParsedDicomFile;
   class ServerIndex;
   class TemporaryFile;
-  
+
+  enum Warnings
+  {
+    Warnings_None,
+    Warnings_001_TagsBeingReadFromStorage,
+    Warnings_002_InconsistentDicomTagsInDb,
+  };
+
+
   class OrthancConfiguration : public boost::noncopyable
   {
   private:
@@ -58,6 +67,7 @@
     Modalities               modalities_;
     Peers                    peers_;
     ServerIndex*             serverIndex_;
+    std::set<Warnings>       disabledWarnings_;
 
     OrthancConfiguration() :
       configurationFileArg_(NULL),
@@ -153,7 +163,9 @@
 
     // "SetServerIndex()" must have been called
     void LoadModalitiesAndPeers();
-    
+
+    void LoadWarnings();
+
     void RegisterFont(ServerResources::FileResourceId resource);
 
     bool LookupStringParameter(std::string& target,
@@ -242,9 +254,10 @@
 
     std::string GetDatabaseServerIdentifier() const;
 
-    bool IsInconsistentDicomTagsLogsEnabled() const;
-
-    bool IsStorageAccessOnFindLogsEnabled() const;
+    bool IsWarningEnabled(Warnings warning) const
+    {
+      return disabledWarnings_.count(warning) == 0;
+    }
 
     static void DefaultExtractDicomSummary(DicomMap& target,
                                            const ParsedDicomFile& dicom);
--- a/OrthancServer/Sources/OrthancInitialization.cpp	Wed Mar 16 09:50:33 2022 +0100
+++ b/OrthancServer/Sources/OrthancInitialization.cpp	Thu Mar 17 17:03:59 2022 +0100
@@ -252,8 +252,24 @@
         {
           const std::string& tagName = content[t].asString();
           DicomTag tag(FromDcmtkBridge::ParseTag(tagName));
-          DicomMap::AddMainDicomTag(tag, tagName, level);
-          LOG(INFO) << "  - " << tagName;
+
+          if (DicomMap::IsComputedTag(tag))
+          {
+            LOG(WARNING) << "  - " << tagName << " can not be added in the Extra Main Dicom Tags since the value of this tag is computed when requested";
+          }
+          else
+          {
+            ValueRepresentation vr = FromDcmtkBridge::LookupValueRepresentation(tag);
+            if (vr == ValueRepresentation_Sequence)
+            {
+              LOG(WARNING) << "  - " << tagName << " can not be added in the Extra Main Dicom Tags since it is a sequence";
+            }
+            else
+            {
+              DicomMap::AddMainDicomTag(tag, tagName, level);
+              LOG(INFO) << "  - " << tagName;
+            }
+          }
         }
       }
     }
@@ -357,6 +373,8 @@
     LoadExternalDictionaries(lock.GetJson());  // New in Orthanc 1.9.4
     LoadCustomDictionary(lock.GetJson());
 
+    lock.GetConfiguration().LoadWarnings();
+
     LoadMainDicomTags(lock.GetJson());  // New in Orthanc 1.11.0
 
     lock.GetConfiguration().RegisterFont(ServerResources::FONT_UBUNTU_MONO_BOLD_16);
--- a/OrthancServer/Sources/ServerContext.cpp	Wed Mar 16 09:50:33 2022 +0100
+++ b/OrthancServer/Sources/ServerContext.cpp	Thu Mar 17 17:03:59 2022 +0100
@@ -1405,7 +1405,7 @@
         // Case (1): The main DICOM tags, as stored in the database,
         // are sufficient to look for match
 
-        if (!GetIndex().GetAllMainDicomTags(allMainDicomTagsFromDB, instances[i]))  // MORE_TAGS: TODO: we could read only the current and upper level to reduce the number of SQL queries
+        if (!GetIndex().GetAllMainDicomTags(allMainDicomTagsFromDB, instances[i]))
         {
           // The instance has been removed during the execution of the
           // lookup, ignore it
@@ -2256,6 +2256,19 @@
   }
 
 
+  static void ComputeInstanceTags(ExpandedResource& resource,
+                                  ServerContext& context,
+                                  const std::string& instancePublicId,
+                                  const std::set<DicomTag>& requestedTags)
+  {
+    if (requestedTags.count(DICOM_TAG_INSTANCE_AVAILABILITY) > 0)
+    {
+      resource.tags_.SetValue(DICOM_TAG_INSTANCE_AVAILABILITY, "ONLINE", false);
+      resource.missingRequestedTags_.erase(DICOM_TAG_INSTANCE_AVAILABILITY);
+    }
+  }
+
+
   static void ComputeSeriesTags(ExpandedResource& resource,
                                 ServerContext& context,
                                 const std::string& seriesPublicId,
@@ -2451,6 +2464,12 @@
     {
       ComputeSeriesTags(resource, context, resourceId, requestedTags);
     }
+
+    if (level == ResourceType_Instance 
+        && DicomMap::HasComputedTags(resource.missingRequestedTags_, ResourceType_Instance))
+    {
+      ComputeInstanceTags(resource, context, resourceId, requestedTags);
+    }
   }
 
   bool ServerContext::ExpandResource(Json::Value& target,
@@ -2537,9 +2556,9 @@
       if (resource.mainDicomTagsSignature_ != DicomMap::GetMainDicomTagsSignature(resource.type_))
       {
         OrthancConfiguration::ReaderLock lock;
-        if (lock.GetConfiguration().IsInconsistentDicomTagsLogsEnabled())
+        if (lock.GetConfiguration().IsWarningEnabled(Warnings_002_InconsistentDicomTagsInDb))
         {
-          LOG(WARNING) << Orthanc::GetResourceTypeText(resource.type_, false , false) << " has been stored with another version of Main Dicom Tags list, you should POST to /" << Orthanc::GetResourceTypeText(resource.type_, true, false) << "/" << resource.id_ << "/reconstruct to update the list of tags saved in DB.  Some MainDicomTags might be missing from this answer.";
+          LOG(WARNING) << "W002: " << Orthanc::GetResourceTypeText(resource.type_, false , false) << " has been stored with another version of Main Dicom Tags list, you should POST to /" << Orthanc::GetResourceTypeText(resource.type_, true, false) << "/" << resource.id_ << "/reconstruct to update the list of tags saved in DB.  Some MainDicomTags might be missing from this answer.";
         }
       }
 
@@ -2547,7 +2566,7 @@
       if (!resource.missingRequestedTags_.empty() && !DicomMap::HasOnlyComputedTags(resource.missingRequestedTags_))
       {
         OrthancConfiguration::ReaderLock lock;
-        if (lock.GetConfiguration().IsStorageAccessOnFindLogsEnabled())
+        if (lock.GetConfiguration().IsWarningEnabled(Warnings_001_TagsBeingReadFromStorage))
         {
           std::set<DicomTag> missingTags;
           Toolbox::AppendSets(missingTags, resource.missingRequestedTags_);
@@ -2562,7 +2581,7 @@
           std::string missings;
           FromDcmtkBridge::FormatListOfTags(missings, missingTags);
 
-          LOG(WARNING) << "PERFORMANCE WARNING: Accessing Dicom tags from storage when accessing " << Orthanc::GetResourceTypeText(resource.type_, false , false) << " : " << missings;
+          LOG(WARNING) << "W001: Accessing Dicom tags from storage when accessing " << Orthanc::GetResourceTypeText(resource.type_, false , false) << " : " << missings;
         }
 
 
--- a/TODO	Wed Mar 16 09:50:33 2022 +0100
+++ b/TODO	Thu Mar 17 17:03:59 2022 +0100
@@ -120,9 +120,6 @@
   - On SCP side: done by https://hg.orthanc-server.com/orthanc/rev/1ec3e1e18f50
   - On SCU side:
     https://groups.google.com/d/msg/orthanc-users/wPl0g5mqZco/5X1Z8tEzBgAJ
-* Support "Instance Availability" (0008,0056) in C-FIND:
-  http://dicom.nema.org/medical/DICOM/2019a/output/chtml/part04/sect_C.4.html#sect_C.4.1.1.3.2
-  https://groups.google.com/d/msg/orthanc-users/hteDgE6igo8/j-ArqD7pBQAJ
 * Check Big Endian transfer syntax in ParsedDicomFile::EmbedImage and
   DicomImageDecoder
 * Strict hierarchical C-FIND:
@@ -160,6 +157,12 @@
   https://groups.google.com/g/orthanc-users/c/aN8nqcRd3jw/m/pmc9ylVeAwAJ.
   One solution could be: Filter first without ModalitiesInStudies and then
   cycle through the responses to filter out with ModalitiesInStudies
+  For C-Find results: we could store the computed tags
+    in metadata on some events like NewSeries + DeletedSeries (same for other computer tags).
+    OtherTags that could be saved in Metadata as well:
+    - ModalitiesInStudy
+    - all computed counters at series/study/patient level
+    - RequestAttributesSequence (sequence that must be included in all DicomWeb QIDO-RS for series)
 
 ========
 Database