changeset 2094:7c3d65166c26

partial integration dicom-sr->mainline
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 08 Nov 2023 14:54:15 +0100
parents 51c8b21b81e4
children ec676dbe85ac
files Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp OrthancStone/Sources/Loaders/LoadedDicomResources.cpp OrthancStone/Sources/Loaders/LoadedDicomResources.h OrthancStone/Sources/StoneEnumerations.cpp OrthancStone/Sources/StoneEnumerations.h OrthancStone/Sources/Toolbox/DicomStructuredReport.cpp OrthancStone/Sources/Toolbox/DicomStructuredReport.h OrthancStone/UnitTestsSources/UnitTestsMain.cpp
diffstat 8 files changed, 285 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Tue Nov 07 18:11:13 2023 +0100
+++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Wed Nov 08 14:54:15 2023 +0100
@@ -204,6 +204,84 @@
 
 
 
+class IFramesCollection : public boost::noncopyable
+{
+public:
+  virtual ~IFramesCollection()
+  {
+  }
+
+  virtual size_t GetFramesCount() const = 0;
+
+  virtual const OrthancStone::DicomInstanceParameters& GetInstanceOfFrame(size_t frameIndex) const = 0;
+
+  virtual unsigned int GetFrameNumberInInstance(size_t frameIndex) const = 0;
+
+  virtual bool LookupFrame(size_t& frameIndex,
+                           const std::string& sopInstanceUid,
+                           unsigned int frameNumber) const = 0;
+
+  virtual bool FindClosestFrame(size_t& frameIndex,
+                                const OrthancStone::Vector& point,
+                                double maximumDistance) const = 0;
+
+  static OrthancStone::CoordinateSystem3D GetFrameGeometry(const IFramesCollection& frames,
+                                                           size_t frameIndex)
+  {
+    return frames.GetInstanceOfFrame(frameIndex).GetFrameGeometry(frames.GetFrameNumberInInstance(frameIndex));
+  }
+};
+
+
+class SortedFramesCollection : public IFramesCollection
+{
+private:
+  std::unique_ptr<OrthancStone::SortedFrames>  frames_;
+
+public:
+  SortedFramesCollection(OrthancStone::SortedFrames* frames)
+  {
+    if (frames == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+    else
+    {
+      frames_.reset(frames);
+    }
+  }
+
+  virtual size_t GetFramesCount() const ORTHANC_OVERRIDE
+  {
+    return frames_->GetFramesCount();
+  }
+
+  const OrthancStone::DicomInstanceParameters& GetInstanceOfFrame(size_t frameIndex) const ORTHANC_OVERRIDE
+  {
+    return frames_->GetInstanceOfFrame(frameIndex);
+  }
+
+  virtual unsigned int GetFrameNumberInInstance(size_t frameIndex) const ORTHANC_OVERRIDE
+  {
+    return frames_->GetFrameNumberInInstance(frameIndex);
+  }
+
+  virtual bool LookupFrame(size_t& frameIndex,
+                           const std::string& sopInstanceUid,
+                           unsigned int frameNumber) const ORTHANC_OVERRIDE
+  {
+    return frames_->LookupFrame(frameIndex, sopInstanceUid, frameNumber);
+  }
+
+  virtual bool FindClosestFrame(size_t& frameIndex,
+                                const OrthancStone::Vector& point,
+                                double maximumDistance) const ORTHANC_OVERRIDE
+  {
+    return frames_->FindClosestFrame(frameIndex, point, maximumDistance);
+  };
+};
+
+
 class VirtualSeries : public boost::noncopyable
 {
 private:
--- a/OrthancStone/Sources/Loaders/LoadedDicomResources.cpp	Tue Nov 07 18:11:13 2023 +0100
+++ b/OrthancStone/Sources/Loaders/LoadedDicomResources.cpp	Wed Nov 08 14:54:15 2023 +0100
@@ -276,4 +276,22 @@
       return true;
     }
   }
+
+
+  bool LoadedDicomResources::LookupResource(Orthanc::DicomMap& target,
+                                            const std::string& id) const
+  {
+    Resources::const_iterator it = resources_.find(id);
+    
+    if (it == resources_.end())
+    {
+      return false;
+    }
+    else
+    {
+      assert(it->second != NULL);
+      target.Assign(it->second->GetDicom());
+      return true;
+    }
+  }
 }
--- a/OrthancStone/Sources/Loaders/LoadedDicomResources.h	Tue Nov 07 18:11:13 2023 +0100
+++ b/OrthancStone/Sources/Loaders/LoadedDicomResources.h	Wed Nov 08 14:54:15 2023 +0100
@@ -146,5 +146,8 @@
     {
       return GetResourceInternal(index).GetSourceJson();
     }
+
+    bool LookupResource(Orthanc::DicomMap& target,
+                        const std::string& id) const;
   };
 }
--- a/OrthancStone/Sources/StoneEnumerations.cpp	Tue Nov 07 18:11:13 2023 +0100
+++ b/OrthancStone/Sources/StoneEnumerations.cpp	Wed Nov 08 14:54:15 2023 +0100
@@ -65,6 +65,10 @@
     {
       return SopClassUid_DicomSeg;
     }
+    else if (s == "1.2.840.10008.5.1.4.1.1.88.33")
+    {
+      return SopClassUid_ComprehensiveSR;
+    }
     else
     {
       //LOG(INFO) << "Other SOP class UID: " << source;
--- a/OrthancStone/Sources/StoneEnumerations.h	Tue Nov 07 18:11:13 2023 +0100
+++ b/OrthancStone/Sources/StoneEnumerations.h	Wed Nov 08 14:54:15 2023 +0100
@@ -116,7 +116,8 @@
     SopClassUid_VideoEndoscopicImageStorage,
     SopClassUid_VideoMicroscopicImageStorage,
     SopClassUid_VideoPhotographicImageStorage,
-    SopClassUid_DicomSeg
+    SopClassUid_DicomSeg,
+    SopClassUid_ComprehensiveSR
   };
 
   enum SeriesThumbnailType
--- a/OrthancStone/Sources/Toolbox/DicomStructuredReport.cpp	Tue Nov 07 18:11:13 2023 +0100
+++ b/OrthancStone/Sources/Toolbox/DicomStructuredReport.cpp	Wed Nov 08 14:54:15 2023 +0100
@@ -118,6 +118,19 @@
 
 namespace OrthancStone
 {
+  void DicomStructuredReport::ReferencedInstance::AddFrame(unsigned int frame)
+  {
+    if (frame == 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      frames_.insert(frame - 1);
+    }
+  }
+
+
   class DicomStructuredReport::Structure : public boost::noncopyable
   {
   private:
@@ -148,7 +161,7 @@
       else
       {
         hasFrameNumber_ = true;
-        frameNumber_ = frame;
+        frameNumber_ = frame - 1;
       }
     }
 
@@ -326,6 +339,10 @@
   {
     DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset();
 
+    studyInstanceUid_ = GetStringValue(dataset, DCM_StudyInstanceUID);
+    seriesInstanceUid_ = GetStringValue(dataset, DCM_SeriesInstanceUID);
+    sopInstanceUid_ = GetStringValue(dataset, DCM_SOPInstanceUID);
+
     CheckStringValue(dataset, DCM_Modality, "SR");
     CheckStringValue(dataset, DCM_SOPClassUID, "1.2.840.10008.5.1.4.1.1.88.33");  // Comprehensive SR IOD
     CheckStringValue(dataset, DCM_ValueType, "CONTAINER");
@@ -369,7 +386,7 @@
 
           if (instancesInformation_.find(sopInstanceUid) == instancesInformation_.end())
           {
-            instancesInformation_[sopInstanceUid] = ReferencedInstance(studyInstanceUid, seriesInstanceUid, sopClassUid);
+            instancesInformation_[sopInstanceUid] = new ReferencedInstance(studyInstanceUid, seriesInstanceUid, sopClassUid);
           }
           else
           {
@@ -468,12 +485,16 @@
                     }
 
                     std::string sopInstanceUid = GetStringValue(*instances.getItem(0), DCM_ReferencedSOPInstanceUID);
-                    if (instancesInformation_.find(sopInstanceUid) == instancesInformation_.end())
+                    std::map<std::string, ReferencedInstance*>::iterator instanceInformation = instancesInformation_.find(sopInstanceUid);
+
+                    if (instanceInformation == instancesInformation_.end())
                     {
                       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
                                                       "Referencing unknown instance in DICOM-SR: " + sopInstanceUid);
                     }
 
+                    assert(instanceInformation->second != NULL);
+
                     if (instances.getItem(0)->tagExists(DCM_ReferencedFrameNumber))
                     {
                       std::string frames = GetStringValue(*instances.getItem(0), DCM_ReferencedFrameNumber);
@@ -490,12 +511,14 @@
                         else
                         {
                           AddStructure(sopInstanceUid, group, true, frame, hasProbabilityOfCancer, probabilityOfCancer);
+                          instanceInformation->second->AddFrame(frame);
                         }
                       }
                     }
                     else
                     {
                       AddStructure(sopInstanceUid, group, false, 0, hasProbabilityOfCancer, probabilityOfCancer);
+                      instanceInformation->second->AddFrame(1);
                     }
                   }
                 }
@@ -515,5 +538,64 @@
       assert(*it != NULL);
       delete *it;
     }
+
+    for (std::map<std::string, ReferencedInstance*>::iterator
+           it = instancesInformation_.begin(); it != instancesInformation_.end(); ++it)
+    {
+      assert(it->second != NULL);
+      delete it->second;
+    }
+  }
+
+
+  void DicomStructuredReport::GetReferencedInstance(std::string& studyInstanceUid,
+                                                    std::string& seriesInstanceUid,
+                                                    std::string& sopInstanceUid,
+                                                    std::string& sopClassUid,
+                                                    size_t i) const
+  {
+    if (i >= orderedInstances_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    sopInstanceUid = orderedInstances_[i];
+
+    std::map<std::string, ReferencedInstance*>::const_iterator found = instancesInformation_.find(sopInstanceUid);
+    if (found == instancesInformation_.end())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    assert(found->second != NULL);
+    studyInstanceUid = found->second->GetStudyInstanceUid();
+    seriesInstanceUid = found->second->GetSeriesInstanceUid();
+    sopClassUid = found->second->GetSopClassUid();
+  }
+
+
+  void DicomStructuredReport::ExportReferencedFrames(std::list<ReferencedFrame>& frames) const
+  {
+    frames.clear();
+
+    for (size_t i = 0; i < orderedInstances_.size(); i++)
+    {
+      std::map<std::string, ReferencedInstance*>::const_iterator found = instancesInformation_.find(orderedInstances_[i]);
+      if (found == instancesInformation_.end())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      assert(found->second != NULL);
+
+      for (std::set<unsigned int>::const_iterator frame = found->second->GetFrames().begin();
+           frame != found->second->GetFrames().end(); ++frame)
+      {
+        frames.push_back(ReferencedFrame(found->second->GetStudyInstanceUid(),
+                                         found->second->GetSeriesInstanceUid(),
+                                         orderedInstances_[i],
+                                         found->second->GetSopClassUid(), *frame));
+      }
+    }
   }
 }
--- a/OrthancStone/Sources/Toolbox/DicomStructuredReport.h	Tue Nov 07 18:11:13 2023 +0100
+++ b/OrthancStone/Sources/Toolbox/DicomStructuredReport.h	Wed Nov 08 14:54:15 2023 +0100
@@ -35,6 +35,7 @@
 
 #include <dcmtk/dcmdata/dcitem.h>
 #include <list>
+#include <set>
 
 namespace OrthancStone
 {
@@ -45,12 +46,13 @@
     class Point;
     class Polyline;
 
-    class ReferencedInstance
+    class ReferencedInstance : public boost::noncopyable
     {
     private:
       std::string  studyInstanceUid_;
       std::string  seriesInstanceUid_;
       std::string  sopClassUid_;
+      std::set<unsigned int>  frames_;
 
     public:
       ReferencedInstance(const std::string& studyInstanceUid,
@@ -62,10 +64,6 @@
       {
       }
 
-      ReferencedInstance()
-      {
-      }
-
       const std::string& GetStudyInstanceUid() const
       {
         return studyInstanceUid_;
@@ -80,8 +78,16 @@
       {
         return sopClassUid_;
       }
+
+      void AddFrame(unsigned int frame);
+
+      const std::set<unsigned int>& GetFrames() const
+      {
+        return frames_;
+      }
     };
 
+
     void AddStructure(const std::string& sopInstanceUid,
                       DcmItem& group,
                       bool hasFrameNumber,
@@ -89,13 +95,93 @@
                       bool hasProbabilityOfCancer,
                       float probabilityOfCancer);
 
-    std::map<std::string, ReferencedInstance>  instancesInformation_;
-    std::vector<std::string>                   orderedInstances_;
-    std::list<Structure*>                      structures_;
+    std::string                                 studyInstanceUid_;
+    std::string                                 seriesInstanceUid_;
+    std::string                                 sopInstanceUid_;
+    std::map<std::string, ReferencedInstance*>  instancesInformation_;
+    std::vector<std::string>                    orderedInstances_;
+    std::list<Structure*>                       structures_;
 
   public:
+    class ReferencedFrame
+    {
+    private:
+      std::string  studyInstanceUid_;
+      std::string  seriesInstanceUid_;
+      std::string  sopInstanceUid_;
+      std::string  sopClassUid_;
+      unsigned int frameNumber_;
+
+    public:
+      ReferencedFrame(const std::string& studyInstanceUid,
+                      const std::string& seriesInstanceUid,
+                      const std::string& sopInstanceUid,
+                      const std::string& sopClassUid,
+                      unsigned int frameNumber) :
+        studyInstanceUid_(studyInstanceUid),
+        seriesInstanceUid_(seriesInstanceUid),
+        sopInstanceUid_(sopInstanceUid),
+        sopClassUid_(sopClassUid),
+        frameNumber_(frameNumber)
+      {
+      }
+
+      const std::string& GetStudyInstanceUid() const
+      {
+        return studyInstanceUid_;
+      }
+
+      const std::string& GetSeriesInstanceUid() const
+      {
+        return seriesInstanceUid_;
+      }
+
+      const std::string& GetSopInstanceUid() const
+      {
+        return sopInstanceUid_;
+      }
+
+      const std::string& GetSopClassUid() const
+      {
+        return sopClassUid_;
+      }
+
+      unsigned int GetFrameNumber() const
+      {
+        return frameNumber_;
+      }
+    };
+
     DicomStructuredReport(Orthanc::ParsedDicomFile& dicom);
 
     ~DicomStructuredReport();
+
+    const std::string& GetStudyInstanceUid() const
+    {
+      return studyInstanceUid_;
+    }
+
+    const std::string& GetSeriesInstanceUid() const
+    {
+      return seriesInstanceUid_;
+    }
+    
+    const std::string& GetSopInstanceUid() const
+    {
+      return sopInstanceUid_;
+    }
+
+    size_t GetReferencedInstancesCount() const
+    {
+      return orderedInstances_.size();
+    }
+
+    void GetReferencedInstance(std::string& studyInstanceUid,
+                               std::string& seriesInstanceUid,
+                               std::string& sopInstanceUid,
+                               std::string& sopClassUid,
+                               size_t i) const;
+
+    void ExportReferencedFrames(std::list<ReferencedFrame>& frames) const;
   };
 }
--- a/OrthancStone/UnitTestsSources/UnitTestsMain.cpp	Tue Nov 07 18:11:13 2023 +0100
+++ b/OrthancStone/UnitTestsSources/UnitTestsMain.cpp	Wed Nov 08 14:54:15 2023 +0100
@@ -44,6 +44,7 @@
   ASSERT_EQ(SopClassUid_VideoEndoscopicImageStorage, StringToSopClassUid("1.2.840.10008.5.1.4.1.1.77.1.1.1"));
   ASSERT_EQ(SopClassUid_VideoMicroscopicImageStorage, StringToSopClassUid("1.2.840.10008.5.1.4.1.1.77.1.2.1"));
   ASSERT_EQ(SopClassUid_VideoPhotographicImageStorage, StringToSopClassUid("1.2.840.10008.5.1.4.1.1.77.1.4.1"));
+  ASSERT_EQ(SopClassUid_ComprehensiveSR, StringToSopClassUid("1.2.840.10008.5.1.4.1.1.88.33"));
   ASSERT_EQ(SopClassUid_Other, StringToSopClassUid("nope"));
 
   ASSERT_EQ(SeriesThumbnailType_Pdf, GetSeriesThumbnailType(SopClassUid_EncapsulatedPdf));