diff OrthancServer/Sources/ServerContext.cpp @ 4940:304514ce84ee more-tags

tools/find + C-Find + list-resources now all using the same code (ExpandResource) to build 'computed tags'
author Alain Mazy <am@osimis.io>
date Tue, 15 Mar 2022 15:57:21 +0100
parents e8a2e145c80e
children 96a3e81eba90
line wrap: on
line diff
--- a/OrthancServer/Sources/ServerContext.cpp	Tue Mar 15 09:09:52 2022 +0100
+++ b/OrthancServer/Sources/ServerContext.cpp	Tue Mar 15 15:57:21 2022 +0100
@@ -2256,6 +2256,203 @@
   }
 
 
+  static void ComputeSeriesTags(ExpandedResource& resource,
+                                ServerContext& context,
+                                const std::string& seriesPublicId,
+                                const std::set<DicomTag>& requestedTags)
+  {
+    if (requestedTags.count(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES) > 0)
+    {
+      ServerIndex& index = context.GetIndex();
+      std::list<std::string> instances;
+
+      index.GetChildren(instances, seriesPublicId);
+
+      resource.tags_.SetValue(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES,
+                              boost::lexical_cast<std::string>(instances.size()), false);
+      resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES);
+    }
+  }
+
+  static void ComputeStudyTags(ExpandedResource& resource,
+                               ServerContext& context,
+                               const std::string& studyPublicId,
+                               const std::set<DicomTag>& requestedTags)
+  {
+    ServerIndex& index = context.GetIndex();
+    std::list<std::string> series;
+    std::list<std::string> instances;
+
+    bool hasNbRelatedSeries = requestedTags.count(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES) > 0;
+    bool hasNbRelatedInstances = requestedTags.count(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) > 0;
+    bool hasModalitiesInStudy = requestedTags.count(DICOM_TAG_MODALITIES_IN_STUDY) > 0;
+    bool hasSopClassesInStudy = requestedTags.count(DICOM_TAG_SOP_CLASSES_IN_STUDY) > 0;
+
+    index.GetChildren(series, studyPublicId);
+
+    if (hasModalitiesInStudy)
+    {
+      std::set<std::string> values;
+
+      for (std::list<std::string>::const_iterator
+            it = series.begin(); it != series.end(); ++it)
+      {
+        DicomMap tags;
+        index.GetMainDicomTags(tags, *it, ResourceType_Series, ResourceType_Series);
+
+        const DicomValue* value = tags.TestAndGetValue(DICOM_TAG_MODALITY);
+
+        if (value != NULL &&
+            !value->IsNull() &&
+            !value->IsBinary())
+        {
+          values.insert(value->GetContent());
+        }
+      }
+
+      std::string modalities;
+      Toolbox::JoinStrings(modalities, values, "\\");
+
+      resource.tags_.SetValue(DICOM_TAG_MODALITIES_IN_STUDY, modalities, false);
+      resource.missingRequestedTags_.erase(DICOM_TAG_MODALITIES_IN_STUDY);
+    }
+
+    if (hasNbRelatedSeries)
+    {
+      resource.tags_.SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES,
+                              boost::lexical_cast<std::string>(series.size()), false);
+      resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES);
+    }
+
+    if (hasNbRelatedInstances || hasSopClassesInStudy)
+    {
+      for (std::list<std::string>::const_iterator
+            it = series.begin(); it != series.end(); ++it)
+      {
+        std::list<std::string> seriesInstancesIds;
+        index.GetChildren(seriesInstancesIds, *it);
+
+        instances.splice(instances.end(), seriesInstancesIds);
+      }
+
+      if (hasNbRelatedInstances)
+      {
+        resource.tags_.SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES,
+                                boost::lexical_cast<std::string>(instances.size()), false);      
+        resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES);
+      }
+
+      if (hasSopClassesInStudy)
+      {
+        std::set<std::string> values;
+
+        for (std::list<std::string>::const_iterator
+              it = instances.begin(); it != instances.end(); ++it)
+        {
+          std::string value;
+
+          if (context.LookupOrReconstructMetadata(value, *it, ResourceType_Instance, MetadataType_Instance_SopClassUid))
+          {
+            values.insert(value);
+          }
+        }
+
+        if (values.size() > 0)
+        {
+          std::string sopClassUids;
+          Toolbox::JoinStrings(sopClassUids, values, "\\");
+          resource.tags_.SetValue(DICOM_TAG_SOP_CLASSES_IN_STUDY, sopClassUids, false);
+        }
+
+        resource.missingRequestedTags_.erase(DICOM_TAG_SOP_CLASSES_IN_STUDY);
+      }
+    }
+  }
+
+  static void ComputePatientTags(ExpandedResource& resource,
+                                 ServerContext& context,
+                                 const std::string& patientPublicId,
+                                 const std::set<DicomTag>& requestedTags)
+  {
+    ServerIndex& index = context.GetIndex();
+
+    std::list<std::string> studies;
+    std::list<std::string> series;
+    std::list<std::string> instances;
+
+    bool hasNbRelatedStudies = requestedTags.count(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES) > 0;
+    bool hasNbRelatedSeries = requestedTags.count(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES) > 0;
+    bool hasNbRelatedInstances = requestedTags.count(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES) > 0;
+
+    index.GetChildren(studies, patientPublicId);
+
+    if (hasNbRelatedStudies)
+    {
+      resource.tags_.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES,
+                              boost::lexical_cast<std::string>(studies.size()), false);
+      resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES);
+    }
+
+    if (hasNbRelatedSeries || hasNbRelatedInstances)
+    {
+      for (std::list<std::string>::const_iterator
+            it = studies.begin(); it != studies.end(); ++it)
+      {
+        std::list<std::string> thisSeriesIds;
+        index.GetChildren(thisSeriesIds, *it);
+        series.splice(series.end(), thisSeriesIds);
+      }
+
+      if (hasNbRelatedSeries)
+      {
+        resource.tags_.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES,
+                                boost::lexical_cast<std::string>(series.size()), false);
+        resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES);
+      }
+    }
+
+    if (hasNbRelatedInstances)
+    {
+      for (std::list<std::string>::const_iterator
+            it = series.begin(); it != series.end(); ++it)
+      {
+        std::list<std::string> thisInstancesIds;
+        index.GetChildren(thisInstancesIds, *it);
+        instances.splice(instances.end(), thisInstancesIds);
+      }
+
+      resource.tags_.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES,
+                              boost::lexical_cast<std::string>(instances.size()), false);
+      resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES);
+    }
+  }
+
+
+  static void ComputeTags(ExpandedResource& resource,
+                          ServerContext& context,
+                          const std::string& resourceId,
+                          ResourceType level,
+                          const std::set<DicomTag>& requestedTags)
+  {
+    if (level == ResourceType_Patient 
+        && DicomMap::HasComputedTags(resource.missingRequestedTags_, ResourceType_Patient))
+    {
+      ComputePatientTags(resource, context, resourceId, requestedTags);
+    }
+
+    if (level == ResourceType_Study 
+        && DicomMap::HasComputedTags(resource.missingRequestedTags_, ResourceType_Study))
+    {
+      ComputeStudyTags(resource, context, resourceId, requestedTags);
+    }
+
+    if (level == ResourceType_Series 
+        && DicomMap::HasComputedTags(resource.missingRequestedTags_, ResourceType_Series))
+    {
+      ComputeSeriesTags(resource, context, resourceId, requestedTags);
+    }
+  }
+
   bool ServerContext::ExpandResource(Json::Value& target,
                                      const std::string& publicId,
                                      ResourceType level,
@@ -2263,22 +2460,24 @@
                                      const std::set<DicomTag>& requestedTags)
   {
     std::string unusedInstanceId;
-    Json::Value unusedValue;
+    Json::Value* unusedDicomAsJson = NULL;
+    DicomMap unusedMainDicomTags;
 
-    return ExpandResource(target, publicId, unusedInstanceId, unusedValue, level, format, requestedTags);
+    return ExpandResource(target, publicId, unusedMainDicomTags, unusedInstanceId, unusedDicomAsJson, level, format, requestedTags);
   }
 
   bool ServerContext::ExpandResource(Json::Value& target,
                                      const std::string& publicId,
+                                     const DicomMap& mainDicomTags,    // optional: the main dicom tags for the resource (if already available)
                                      const std::string& instanceId,    // optional: the id of an instance for the resource (if already available)
-                                     const Json::Value& dicomAsJson,   // optional: the dicom-as-json for the resource (if already available)
+                                      const Json::Value* dicomAsJson,  // optional: the dicom-as-json for the resource (if already available)
                                      ResourceType level,
                                      DicomToJsonFormat format,
                                      const std::set<DicomTag>& requestedTags)
   {
     ExpandedResource resource;
 
-    if (ExpandResource(resource, publicId, instanceId, dicomAsJson, level, requestedTags))
+    if (ExpandResource(resource, publicId, mainDicomTags, instanceId, dicomAsJson, level, requestedTags, ExpandResourceDbFlags_Default))
     {
       SerializeExpandedResource(target, resource, format, requestedTags);
       return true;
@@ -2289,12 +2488,50 @@
   
   bool ServerContext::ExpandResource(ExpandedResource& resource,
                                      const std::string& publicId,
+                                     const DicomMap& mainDicomTags,    // optional: the main dicom tags for the resource (if already available)
                                      const std::string& instanceId,    // optional: the id of an instance for the resource (if already available)
-                                     const Json::Value& dicomAsJson,   // optional: the dicom-as-json for the resource (if already available)
+                                     const Json::Value* dicomAsJson,   // optional: the dicom-as-json for the resource (if already available)
                                      ResourceType level,
-                                     const std::set<DicomTag>& requestedTags)
+                                     const std::set<DicomTag>& requestedTags,
+                                     ExpandResourceDbFlags expandFlags)
   {
-    if (GetIndex().ExpandResource(resource, publicId, level, requestedTags))
+    // first try to get the tags from what is already available
+    
+    if ((expandFlags & ExpandResourceDbFlags_IncludeMainDicomTags)
+      && (mainDicomTags.GetSize() > 0)
+      && (dicomAsJson != NULL))
+    {
+      
+      if (mainDicomTags.GetSize() > 0)
+      {
+        resource.tags_.Merge(mainDicomTags);
+      }
+
+      if (dicomAsJson != NULL && dicomAsJson->isObject())
+      {
+        resource.tags_.FromDicomAsJson(*dicomAsJson);
+      }
+
+      std::set<DicomTag> retrievedTags;
+      std::set<DicomTag> missingTags;
+      resource.tags_.GetTags(retrievedTags);
+
+      Toolbox::GetMissingsFromSet(missingTags, requestedTags, retrievedTags);
+
+      // if all possible tags have been read, no need to get them from DB anymore
+      if (missingTags.size() == 0 || DicomMap::HasOnlyComputedTags(missingTags))
+      {
+        expandFlags = static_cast<ExpandResourceDbFlags>(expandFlags & ~ExpandResourceDbFlags_IncludeMainDicomTags);
+      }
+
+      if (missingTags.size() == 0 && expandFlags == ExpandResourceDbFlags_None)  // we have already retrieved anything we need
+      {
+        return true;
+      }
+    }
+
+    if (expandFlags != ExpandResourceDbFlags_None
+        && GetIndex().ExpandResource(resource, publicId, level, requestedTags, expandFlags))
     {
       // check the main dicom tags list has not changed since the resource was stored
       if (resource.mainDicomTagsSignature_ != DicomMap::GetMainDicomTagsSignature(resource.type_))
@@ -2307,11 +2544,12 @@
       }
 
       // possibly merge missing requested tags from dicom-as-json
-      if (!resource.missingRequestedTags_.empty())
+      if (!resource.missingRequestedTags_.empty() && !DicomMap::HasOnlyComputedTags(resource.missingRequestedTags_))
       {
         std::string instanceId_ = instanceId;
-        Json::Value dicomAsJson_ = dicomAsJson;
-        if (dicomAsJson_.isNull())
+        DicomMap tagsFromJson;
+
+        if (dicomAsJson == NULL)
         {
           if (instanceId_.empty())
           {
@@ -2331,20 +2569,28 @@
             }
           }
   
-          // MORE_TAGS :TODO: log warning (add an option to disable them)
-          ReadDicomAsJson(dicomAsJson_, instanceId_);
+          // MORE_TAGS :TODO: log "performance" warning (add an option to disable them)
+          Json::Value tmpDicomAsJson;
+          ReadDicomAsJson(tmpDicomAsJson, instanceId_);
+          tagsFromJson.FromDicomAsJson(tmpDicomAsJson);
+        }
+        else
+        {
+          tagsFromJson.FromDicomAsJson(*dicomAsJson);
         }
 
-        DicomMap allTags;
-        allTags.FromDicomAsJson(dicomAsJson_);
-
-        resource.tags_.Merge(allTags);
+        resource.tags_.Merge(tagsFromJson);
       }
 
-      return true;
+      // compute the requested tags
+      ComputeTags(resource, *this, publicId, level, requestedTags);
+    }
+    else
+    {
+      return false;
     }
 
-    return false;
+    return true;
   }
 
 }