changeset 1822:0489fe25ce48

support of pixel spacing in ultrasound images from tag SequenceOfUltrasoundRegions
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 26 May 2021 18:13:35 +0200
parents 36430d73e36c
children 781e9fc8925e
files Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewerApplication.h Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp OrthancStone/Sources/Loaders/LoadedDicomResources.cpp OrthancStone/Sources/Loaders/LoadedDicomResources.h OrthancStone/Sources/Toolbox/DicomInstanceParameters.cpp OrthancStone/Sources/Toolbox/DicomInstanceParameters.h
diffstat 6 files changed, 243 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewerApplication.h	Wed May 26 14:02:12 2021 +0200
+++ b/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewerApplication.h	Wed May 26 18:13:35 2021 +0200
@@ -156,9 +156,11 @@
        * "PixelSpacing" in the "SequenceOfUltrasoundRegions"
        * (0018,6011) sequence, cf. tags "PhysicalDeltaX" (0018,602c)
        * and "PhysicalDeltaY" (0018,602e) => This would require
-       * storing the full JSON into the "LoadedDicomResources" class
-       * or to use DCMTK
+       * parsing "message.GetResources()->GetSourceJson(0)"
+       * => cf. "DicomInstanceParameters::EnrichUsingDicomWeb()"
        **/
+
+      // std::cout << message.GetResources()->GetSourceJson(0).toStyledString();
       
       LOG(INFO) << "Using millimeters units, as the DICOM instance contains the PixelSpacing tag";
       units_ = OrthancStone::Units_Millimeters;
--- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Wed May 26 14:02:12 2021 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Wed May 26 18:13:35 2021 +0200
@@ -1399,6 +1399,30 @@
       
       {
         OrthancStone::DicomInstanceParameters params(dicom);
+        
+        params.EnrichUsingDicomWeb(message.GetResources()->GetSourceJson(0));
+        GetViewport().centralPixelSpacingX_ = params.GetPixelSpacingX();
+        GetViewport().centralPixelSpacingY_ = params.GetPixelSpacingY();
+
+        if (params.HasPixelSpacing())
+        {
+          GetViewport().stoneAnnotations_->SetUnits(OrthancStone::Units_Millimeters);
+        }
+        else
+        {
+          GetViewport().stoneAnnotations_->SetUnits(OrthancStone::Units_Pixels);
+        }
+
+        if (params.GetPixelSpacingX() != 0 &&
+            params.GetPixelSpacingY() != 0 &&
+            params.GetWidth() != 0 &&
+            params.GetHeight())
+        {
+          GetViewport().centralPhysicalWidth_ = (params.GetPixelSpacingX() *
+                                                 static_cast<double>(params.GetWidth()));
+          GetViewport().centralPhysicalHeight_ = (params.GetPixelSpacingY() *
+                                                  static_cast<double>(params.GetHeight()));
+        }
 
         GetViewport().windowingPresetCenters_.resize(params.GetWindowingPresetsCount());
         GetViewport().windowingPresetWidths_.resize(params.GetWindowingPresetsCount());
@@ -1703,6 +1727,8 @@
   bool                                         synchronizationEnabled_;
   double                                       centralPhysicalWidth_;   // LSD-479
   double                                       centralPhysicalHeight_;
+  double                                       centralPixelSpacingX_;
+  double                                       centralPixelSpacingY_;
 
   bool         hasFocusOnInstance_;
   std::string  focusSopInstanceUid_;
@@ -1856,7 +1882,17 @@
     layer->SetFlipY(flipY_);
 
     double pixelSpacingX, pixelSpacingY;
-    OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX, pixelSpacingY, instance.GetTags());
+
+    if (instance.HasPixelSpacing())
+    {
+      pixelSpacingX = instance.GetPixelSpacingX();
+      pixelSpacingY = instance.GetPixelSpacingY();
+    }
+    else
+    {
+      pixelSpacingX = centralPixelSpacingX_;
+      pixelSpacingY = centralPixelSpacingY_;
+    }
 
     if (FIX_LSD_479)
     {
@@ -2144,7 +2180,9 @@
     synchronizationOffset_(OrthancStone::LinearAlgebra::CreateVector(0, 0, 0)),
     synchronizationEnabled_(false),
     centralPhysicalWidth_(1),
-    centralPhysicalHeight_(1)
+    centralPhysicalHeight_(1),
+    centralPixelSpacingX_(1),
+    centralPixelSpacingY_(1)
   {
     if (!framesCache_)
     {
@@ -2420,17 +2458,6 @@
           boost::make_shared<OrthancStone::LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID),
           0, source_, uri, new LoadSeriesDetailsFromInstance(GetSharedObserver()));
       }
-
-      if (centralInstance.GetPixelSpacingX() != 0 &&
-          centralInstance.GetPixelSpacingY() != 0 &&
-          centralInstance.GetWidth() != 0 &&
-          centralInstance.GetHeight())
-      {
-        centralPhysicalWidth_ = (centralInstance.GetPixelSpacingX() *
-                                 static_cast<double>(centralInstance.GetWidth()));
-        centralPhysicalHeight_ = (centralInstance.GetPixelSpacingY() *
-                                  static_cast<double>(centralInstance.GetHeight()));
-      }
     }
 
     ApplyScheduledFocus();
--- a/OrthancStone/Sources/Loaders/LoadedDicomResources.cpp	Wed May 26 14:02:12 2021 +0200
+++ b/OrthancStone/Sources/Loaders/LoadedDicomResources.cpp	Wed May 26 18:13:35 2021 +0200
@@ -29,7 +29,58 @@
 
 namespace OrthancStone
 {
-  void LoadedDicomResources::Flatten()
+  LoadedDicomResources::Resource::Resource(const Orthanc::DicomMap& dicom) :
+    dicom_(dicom.Clone())
+  {
+    if (dicom_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  LoadedDicomResources::Resource* LoadedDicomResources::Resource::Clone() const
+  {
+    assert(dicom_.get() != NULL);
+    return new Resource(*dicom_);
+  }
+
+
+  const Json::Value& LoadedDicomResources::Resource::GetSourceJson() const
+  {
+    if (sourceJson_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return *sourceJson_;
+    }
+  }
+
+
+  void LoadedDicomResources::Resource::SetSourceJson(const Json::Value& json)
+  {
+    sourceJson_.reset(new Json::Value(json));
+  }
+
+
+  void LoadedDicomResources::AddResourceInternal(Resource* resource)
+  {
+    std::unique_ptr<Resource> protection(resource);
+    
+    std::string id;
+    
+    if (protection->GetDicom().LookupStringValue(id, indexedTag_, false /* no binary value */) &&
+        resources_.find(id) == resources_.end() /* Don't index twice the same resource */)
+    {
+      resources_[id] = protection.release();
+      flattened_.clear();   // Invalidate the flattened version 
+    }
+  }
+
+
+  const LoadedDicomResources::Resource& LoadedDicomResources::GetResourceInternal(size_t index)
   {
     // Lazy generation of a "std::vector" from the "std::map"
     if (flattened_.empty())
@@ -48,6 +99,16 @@
       // No need to flatten
       assert(flattened_.size() == resources_.size());
     }
+
+    if (index >= flattened_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      assert(flattened_[index] != NULL);
+      return *flattened_[index];
+    }
   }
 
 
@@ -56,7 +117,10 @@
     assert(dicomweb.type() == Json::objectValue);
     Orthanc::DicomMap dicom;
     dicom.FromDicomWeb(dicomweb);
-    AddResource(dicom);
+
+    std::unique_ptr<Resource> resource(new Resource(dicom));
+    resource->SetSourceJson(dicomweb);
+    AddResourceInternal(resource.release());
   }
 
   
@@ -68,7 +132,7 @@
          it != other.resources_.end(); ++it)
     {
       assert(it->second != NULL);
-      AddResource(*it->second);
+      AddResourceInternal(it->second->Clone());
     }
   }
 
@@ -85,22 +149,6 @@
   }
 
 
-  Orthanc::DicomMap& LoadedDicomResources::GetResource(size_t index)
-  {
-    Flatten();
-
-    if (index >= flattened_.size())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      assert(flattened_[index] != NULL);
-      return *flattened_[index];
-    }
-  }
-
-
   void LoadedDicomResources::MergeResource(Orthanc::DicomMap& target,
                                            const std::string& id) const
   {
@@ -113,7 +161,7 @@
     else
     {
       assert(it->second != NULL);
-      target.Merge(*it->second);
+      target.Merge(it->second->GetDicom());
     }
   }
   
@@ -131,21 +179,14 @@
     else
     {
       assert(found->second != NULL);
-      return found->second->LookupStringValue(target, tag, false);
+      return found->second->GetDicom().LookupStringValue(target, tag, false);
     }  
   }
 
   
   void LoadedDicomResources::AddResource(const Orthanc::DicomMap& dicom)
   {
-    std::string id;
-    
-    if (dicom.LookupStringValue(id, indexedTag_, false /* no binary value */) &&
-        resources_.find(id) == resources_.end() /* Don't index twice the same resource */)
-    {
-      resources_[id] = dicom.Clone();
-      flattened_.clear();   // Invalidate the flattened version 
-    }
+    AddResourceInternal(new Resource(dicom));
   }
 
 
@@ -153,7 +194,10 @@
   {
     Orthanc::DicomMap dicom;
     dicom.FromDicomAsJson(tags);
-    AddResource(dicom);
+
+    std::unique_ptr<Resource> resource(new Resource(dicom));
+    resource->SetSourceJson(tags);
+    AddResourceInternal(resource.release());
   }
 
 
@@ -196,7 +240,7 @@
       assert(it->second != NULL);
       
       std::string value;
-      if (it->second->LookupStringValue(value, tag, false))
+      if (it->second->GetDicom().LookupStringValue(value, tag, false))
       {
         Counter::iterator found = counter.find(value);
         if (found == counter.end())
--- a/OrthancStone/Sources/Loaders/LoadedDicomResources.h	Wed May 26 14:02:12 2021 +0200
+++ b/OrthancStone/Sources/Loaders/LoadedDicomResources.h	Wed May 26 18:13:35 2021 +0200
@@ -34,13 +34,41 @@
   class LoadedDicomResources : public boost::noncopyable
   {
   private:
-    typedef std::map<std::string, Orthanc::DicomMap*>  Resources;
+    class Resource : public boost::noncopyable
+    {
+    private:
+      std::unique_ptr<Orthanc::DicomMap>  dicom_;
+      std::unique_ptr<Json::Value>        sourceJson_;
+
+    public:
+      Resource(const Orthanc::DicomMap& dicom);
+
+      Resource* Clone() const;
+
+      const Orthanc::DicomMap& GetDicom() const
+      {
+        return *dicom_;
+      }
 
-    Orthanc::DicomTag                indexedTag_;
-    Resources                        resources_;
-    std::vector<Orthanc::DicomMap*>  flattened_;
+      bool HasSourceJson() const
+      {
+        return sourceJson_.get() != NULL;
+      }
+
+      const Json::Value& GetSourceJson() const;
 
-    void Flatten();
+      void SetSourceJson(const Json::Value& json);
+    };
+    
+    typedef std::map<std::string, Resource*>  Resources;
+
+    Orthanc::DicomTag       indexedTag_;
+    Resources               resources_;
+    std::vector<Resource*>  flattened_;
+
+    void AddResourceInternal(Resource* resource);
+
+    const Resource& GetResourceInternal(size_t index);
 
     void AddFromDicomWebInternal(const Json::Value& dicomweb);
 
@@ -71,7 +99,10 @@
       return resources_.size();
     }
 
-    Orthanc::DicomMap& GetResource(size_t index);
+    const Orthanc::DicomMap& GetResource(size_t index)
+    {
+      return GetResourceInternal(index).GetDicom();
+    }
 
     bool HasResource(const std::string& id) const
     {
@@ -93,5 +124,15 @@
 
     bool LookupTagValueConsensus(std::string& target,
                                  const Orthanc::DicomTag& tag) const;
+
+    bool HasSourceJson(size_t index)
+    {
+      return GetResourceInternal(index).HasSourceJson();
+    }
+
+    const Json::Value& GetSourceJson(size_t index)
+    {
+      return GetResourceInternal(index).GetSourceJson();
+    }
   };
 }
--- a/OrthancStone/Sources/Toolbox/DicomInstanceParameters.cpp	Wed May 26 14:02:12 2021 +0200
+++ b/OrthancStone/Sources/Toolbox/DicomInstanceParameters.cpp	Wed May 26 18:13:35 2021 +0200
@@ -706,4 +706,76 @@
       return true;
     }
   }
+
+  
+  void DicomInstanceParameters::SetPixelSpacing(double pixelSpacingX,
+                                                double pixelSpacingY)
+  {
+    data_.hasPixelSpacing_ = true;
+    data_.pixelSpacingX_ = pixelSpacingX;
+    data_.pixelSpacingY_ = pixelSpacingY;
+  }
+
+
+  static const Json::Value* LookupDicomWebSingleValue(const Json::Value& dicomweb,
+                                                      const std::string& tag,
+                                                      const std::string& vr)
+  {
+    static const char* const VALUE = "Value";
+    static const char* const VR = "vr";
+
+    if (dicomweb.type() == Json::objectValue &&
+        dicomweb.isMember(tag) &&
+        dicomweb[tag].type() == Json::objectValue &&
+        dicomweb[tag].isMember(VALUE) &&
+        dicomweb[tag].isMember(VR) &&
+        dicomweb[tag][VR].type() == Json::stringValue &&
+        dicomweb[tag][VR].asString() == vr &&
+        dicomweb[tag][VALUE].type() == Json::arrayValue &&
+        dicomweb[tag][VALUE].size() == 1u)
+    {
+      return &dicomweb[tag][VALUE][0];
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+
+
+  void DicomInstanceParameters::EnrichUsingDicomWeb(const Json::Value& dicomweb)
+  {
+    /**
+     * Use DICOM tag "SequenceOfUltrasoundRegions" (0018,6011) in
+     * order to derive the pixel spacing on ultrasound (US) images
+     **/
+    
+    if (!data_.hasPixelSpacing_)
+    {
+      const Json::Value* region = LookupDicomWebSingleValue(dicomweb, "00186011", "SQ");
+      if (region != NULL)
+      {
+        const Json::Value* physicalUnitsXDirection = LookupDicomWebSingleValue(*region, "00186024", "US");
+        const Json::Value* physicalUnitsYDirection = LookupDicomWebSingleValue(*region, "00186026", "US");
+        const Json::Value* physicalDeltaX = LookupDicomWebSingleValue(*region, "0018602C", "FD");
+        const Json::Value* physicalDeltaY = LookupDicomWebSingleValue(*region, "0018602E", "FD");
+        
+        if (physicalUnitsXDirection != NULL &&
+            physicalUnitsYDirection != NULL &&
+            physicalDeltaX != NULL &&
+            physicalDeltaY != NULL &&
+            physicalUnitsXDirection->type() == Json::intValue &&
+            physicalUnitsYDirection->type() == Json::intValue &&
+            physicalUnitsXDirection->asInt() == 0x0003 &&  // Centimeters
+            physicalUnitsYDirection->asInt() == 0x0003 &&  // Centimeters
+            physicalDeltaX->isNumeric() &&
+            physicalDeltaY->isNumeric())
+        {
+          // Scene coordinates are expressed in millimeters => multiplication by 10
+          SetPixelSpacing(10.0 * physicalDeltaX->asDouble(),
+                          10.0 * physicalDeltaY->asDouble());
+        }
+      }
+    }
+  }
 }
--- a/OrthancStone/Sources/Toolbox/DicomInstanceParameters.h	Wed May 26 14:02:12 2021 +0200
+++ b/OrthancStone/Sources/Toolbox/DicomInstanceParameters.h	Wed May 26 18:13:35 2021 +0200
@@ -235,5 +235,10 @@
     {
       return data_.hasPixelSpacing_;
     }
+
+    void SetPixelSpacing(double pixelSpacingX,
+                         double pixelSpacingY);
+
+    void EnrichUsingDicomWeb(const Json::Value& dicomweb);
   };
 }