changeset 1898:e018037d4d0e

Support of optional tags for counting resources in C-Find
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 21 Dec 2015 19:26:38 +0100
parents 7110e2881dc0
children 6615133a996c
files Core/DicomFormat/DicomTag.h NEWS OrthancServer/OrthancFindRequestHandler.cpp OrthancServer/ServerIndex.cpp OrthancServer/ServerIndex.h
diffstat 5 files changed, 332 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/Core/DicomFormat/DicomTag.h	Wed Dec 16 09:23:52 2015 +0100
+++ b/Core/DicomFormat/DicomTag.h	Mon Dec 21 19:26:38 2015 +0100
@@ -169,4 +169,14 @@
   static const DicomTag DICOM_TAG_IMAGE_COMMENTS(0x0020, 0x4000);
   static const DicomTag DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION(0x0018, 0x1400);
   static const DicomTag DICOM_TAG_CONTRAST_BOLUS_AGENT(0x0018, 0x0010);
+
+  // Counting patients, studies and series
+  // https://www.medicalconnections.co.uk/kb/Counting_Studies_Series_and_Instances
+  static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES(0x0020, 0x1200);  
+  static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES(0x0020, 0x1202);  
+  static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES(0x0020, 0x1204);  
+  static const DicomTag DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES(0x0020, 0x1206);  
+  static const DicomTag DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES(0x0020, 0x1208);  
+  static const DicomTag DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES(0x0020, 0x1209);  
+  static const DicomTag DICOM_TAG_SOP_CLASSES_IN_STUDY(0x0008, 0x0062);  
 }
--- a/NEWS	Wed Dec 16 09:23:52 2015 +0100
+++ b/NEWS	Mon Dec 21 19:26:38 2015 +0100
@@ -1,6 +1,9 @@
 Pending changes in the mainline
 ===============================
 
+* Support of optional tags for counting resources in C-Find:
+  0008-0061, 0008-0062, 0020-1200, 0020-1202, 0020-1204, 0020-1206, 0020-1208, 0020-1209
+
 
 Version 1.0.0 (2015/12/15)
 ==========================
--- a/OrthancServer/OrthancFindRequestHandler.cpp	Wed Dec 16 09:23:52 2015 +0100
+++ b/OrthancServer/OrthancFindRequestHandler.cpp	Mon Dec 21 19:26:38 2015 +0100
@@ -46,10 +46,281 @@
 
 namespace Orthanc
 {
+  static void GetChildren(std::list<std::string>& target,
+                          ServerIndex& index,
+                          const std::list<std::string>& source)
+  {
+    target.clear();
+
+    for (std::list<std::string>::const_iterator
+           it = source.begin(); it != source.end(); ++it)
+    {
+      std::list<std::string> tmp;
+      index.GetChildren(tmp, *it);
+      target.splice(target.end(), tmp);
+    }
+  }
+
+
+  static void StoreSetOfStrings(DicomMap& result,
+                                const DicomTag& tag,
+                                const std::set<std::string>& values)
+  {
+    bool isFirst = true;
+
+    std::string s;
+    for (std::set<std::string>::const_iterator
+           it = values.begin(); it != values.end(); ++it)
+    {
+      if (isFirst)
+      {
+        isFirst = false;
+      }
+      else
+      {
+        s += "\\";
+      }
+
+      s += *it;
+    }
+
+    result.SetValue(tag, s);
+  }
+
+
+  static void ExtractTagFromMainDicomTags(std::set<std::string>& target,
+                                          ServerIndex& index,
+                                          const DicomTag& tag,
+                                          const std::list<std::string>& resources,
+                                          ResourceType level)
+  {
+    for (std::list<std::string>::const_iterator
+           it = resources.begin(); it != resources.end(); ++it)
+    {
+      DicomMap tags;
+      if (index.GetMainDicomTags(tags, *it, level, level) &&
+          tags.HasTag(tag))
+      {
+        target.insert(tags.GetValue(tag).GetContent());
+      }
+    }
+  }
+
+
+  static void ExtractTagFromInstances(std::set<std::string>& target,
+                                      ServerContext& context,
+                                      const DicomTag& tag,
+                                      const std::list<std::string>& instances)
+  {
+    std::string formatted = tag.Format();
+
+    for (std::list<std::string>::const_iterator
+           it = instances.begin(); it != instances.end(); ++it)
+    {
+      Json::Value dicom;
+      context.ReadJson(dicom, *it);
+
+      if (dicom.isMember(formatted))
+      {
+        const Json::Value& source = dicom[formatted];
+
+        if (source.type() == Json::objectValue &&
+            source.isMember("Type") &&
+            source.isMember("Value") &&
+            source["Type"].asString() == "String" &&
+            source["Value"].type() == Json::stringValue)
+        {
+          target.insert(source["Value"].asString());
+        }
+      }
+    }
+  }
+
+
+  static void ComputePatientCounters(DicomMap& result,
+                                     ServerIndex& index,
+                                     const std::string& patient,
+                                     const DicomMap& query)
+  {
+    std::list<std::string> studies;
+    index.GetChildren(studies, patient);
+
+    if (query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES))
+    {
+      result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES,
+                      boost::lexical_cast<std::string>(studies.size()));
+    }
+
+    if (!query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES) &&
+        !query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES))
+    {
+      return;
+    }
+
+    std::list<std::string> series;
+    GetChildren(series, index, studies);
+    studies.clear();  // This information is useless below
+    
+    if (query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES))
+    {
+      result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES,
+                      boost::lexical_cast<std::string>(series.size()));
+    }
+
+    if (!query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES))
+    {
+      return;
+    }
+
+    std::list<std::string> instances;
+    GetChildren(instances, index, series);
+
+    if (query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES))
+    {
+      result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES,
+                      boost::lexical_cast<std::string>(instances.size()));
+    }
+  }
+
+
+  static void ComputeStudyCounters(DicomMap& result,
+                                   ServerContext& context,
+                                   const std::string& study,
+                                   const DicomMap& query)
+  {
+    ServerIndex& index = context.GetIndex();
+
+    std::list<std::string> series;
+    index.GetChildren(series, study);
+    
+    if (query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES))
+    {
+      result.SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES,
+                      boost::lexical_cast<std::string>(series.size()));
+    }
+
+    if (query.HasTag(DICOM_TAG_MODALITIES_IN_STUDY))
+    {
+      std::set<std::string> values;
+      ExtractTagFromMainDicomTags(values, index, DICOM_TAG_MODALITY, series, ResourceType_Series);
+      StoreSetOfStrings(result, DICOM_TAG_MODALITIES_IN_STUDY, values);
+    }
+
+    if (!query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) &&
+        !query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY))
+    {
+      return;
+    }
+
+    std::list<std::string> instances;
+    GetChildren(instances, index, series);
+
+    if (query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES))
+    {
+      result.SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES,
+                      boost::lexical_cast<std::string>(instances.size()));
+    }
+
+    if (query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY))
+    {
+      std::set<std::string> values;
+      ExtractTagFromInstances(values, context, DICOM_TAG_SOP_CLASS_UID, instances);
+      StoreSetOfStrings(result, DICOM_TAG_SOP_CLASSES_IN_STUDY, values);
+    }
+  }
+
+
+  static void ComputeSeriesCounters(DicomMap& result,
+                                    ServerIndex& index,
+                                    const std::string& series,
+                                    const DicomMap& query)
+  {
+    std::list<std::string> instances;
+    index.GetChildren(instances, series);
+
+    if (query.HasTag(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES))
+    {
+      result.SetValue(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES,
+                      boost::lexical_cast<std::string>(instances.size()));
+    }
+  }
+
+
+  static DicomMap* ComputeCounters(ServerContext& context,
+                                   const std::string& instanceId,
+                                   ResourceType level,
+                                   const DicomMap& query)
+  {
+    switch (level)
+    {
+      case ResourceType_Patient:
+        if (!query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES) &&
+            !query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES) &&
+            !query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES))
+        {
+          return NULL;
+        }
+
+        break;
+
+      case ResourceType_Study:
+        if (!query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES) &&
+            !query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) &&
+            !query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY) &&
+            !query.HasTag(DICOM_TAG_MODALITIES_IN_STUDY))
+        {
+          return NULL;
+        }
+
+        break;
+
+      case ResourceType_Series:
+        if (!query.HasTag(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES))
+        {
+          return NULL;
+        }
+
+        break;
+
+      default:
+        return NULL;
+    }
+
+    std::string parent;
+    if (!context.GetIndex().LookupParent(parent, instanceId, level))
+    {
+      throw OrthancException(ErrorCode_UnknownResource);  // The resource was deleted in between
+    }
+
+    std::auto_ptr<DicomMap> result(new DicomMap);
+
+    switch (level)
+    {
+      case ResourceType_Patient:
+        ComputePatientCounters(*result, context.GetIndex(), parent, query);
+        break;
+
+      case ResourceType_Study:
+        ComputeStudyCounters(*result, context, parent, query);
+        break;
+
+      case ResourceType_Series:
+        ComputeSeriesCounters(*result, context.GetIndex(), parent, query);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+
+    return result.release();
+  }
+
+
   static void AddAnswer(DicomFindAnswers& answers,
                         const Json::Value& resource,
                         const DicomArray& query,
-                        const std::list<DicomTag>& sequencesToReturn)
+                        const std::list<DicomTag>& sequencesToReturn,
+                        const DicomMap* counters)
   {
     DicomMap result;
 
@@ -80,6 +351,15 @@
       }
     }
 
+    if (counters != NULL)
+    {
+      DicomArray tmp(*counters);
+      for (size_t i = 0; i < tmp.GetSize(); i++)
+      {
+        result.SetValue(tmp.GetElement(i).GetTag(), tmp.GetElement(i).GetValue().GetContent());
+      }
+    }
+
     if (result.GetSize() == 0 &&
         sequencesToReturn.empty())
     {
@@ -96,8 +376,6 @@
       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 &&
@@ -379,7 +657,8 @@
         }
         else
         {
-          AddAnswer(answers, dicom, query, sequencesToReturn);
+          std::auto_ptr<DicomMap> counters(ComputeCounters(context_, instances[i], level, input));
+          AddAnswer(answers, dicom, query, sequencesToReturn, counters.get());
         }
       }
     }
--- a/OrthancServer/ServerIndex.cpp	Wed Dec 16 09:23:52 2015 +0100
+++ b/OrthancServer/ServerIndex.cpp	Mon Dec 21 19:26:38 2015 +0100
@@ -2177,4 +2177,36 @@
       instances[pos] = db_.GetPublicId(instance);
     }
   }
+
+
+  bool ServerIndex::LookupParent(std::string& target,
+                                 const std::string& publicId,
+                                 ResourceType parentType)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    ResourceType type;
+    int64_t id;
+    if (!db_.LookupResource(id, type, publicId))
+    {
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+
+    while (type != parentType)
+    {
+      int64_t parentId;
+
+      if (type == ResourceType_Patient ||    // Cannot further go up in hierarchy
+          !db_.LookupParent(parentId, id))
+      {
+        return false;
+      }
+
+      id = parentId;
+      type = GetParentResourceType(type);
+    }
+
+    target = db_.GetPublicId(id);
+    return true;
+  }
 }
--- a/OrthancServer/ServerIndex.h	Wed Dec 16 09:23:52 2015 +0100
+++ b/OrthancServer/ServerIndex.h	Mon Dec 21 19:26:38 2015 +0100
@@ -264,5 +264,9 @@
     void FindCandidates(std::vector<std::string>& resources,
                         std::vector<std::string>& instances,
                         const ::Orthanc::LookupResource& lookup);
+
+    bool LookupParent(std::string& target,
+                      const std::string& publicId,
+                      ResourceType parentType);
   };
 }