changeset 1451:1df1c126fb36

Merge the loader plugin injection feature
author Benjamin Golinvaux <bgo@osimis.io>
date Tue, 02 Jun 2020 16:46:12 +0200
parents 4e233e3ea53b (current diff) 2a5f0f771fbd (diff)
children 2a11bbf7f6b0
files
diffstat 7 files changed, 655 insertions(+), 114 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Loaders/DicomStructureSetLoader.cpp	Tue May 19 15:31:38 2020 +0200
+++ b/Framework/Loaders/DicomStructureSetLoader.cpp	Tue Jun 02 16:46:12 2020 +0200
@@ -48,8 +48,37 @@
   }
 #endif
 
+  // implementation of IInstanceLookupHandler that uses Orthanc REST API calls to retrive the 
+  // geometry of referenced instances
+  class DicomStructureSetLoader::RestInstanceLookupHandler : public DicomStructureSetLoader::IInstanceLookupHandler,
+    public LoaderStateMachine
+  {
+  public:
+    static boost::shared_ptr<RestInstanceLookupHandler > Create(DicomStructureSetLoader& loader)
+    {
+      boost::shared_ptr<RestInstanceLookupHandler> obj(new RestInstanceLookupHandler(loader));
+      obj->LoaderStateMachine::PostConstructor();
+      return obj;
+    }
 
-  class DicomStructureSetLoader::AddReferencedInstance : public LoaderStateMachine::State
+  protected:
+    RestInstanceLookupHandler(DicomStructureSetLoader& loader) 
+      : LoaderStateMachine(loader.loadersContext_)
+      , loader_(loader)
+    {
+    }
+
+    virtual void RetrieveReferencedSlices(const std::set<std::string>& nonEmptyInstances) ORTHANC_OVERRIDE;
+
+  private:
+    // these subclasses hold the loading state
+    class AddReferencedInstance;   // 2nd state
+    class LookupInstance;          // 1st state
+
+    DicomStructureSetLoader& loader_;
+  };
+
+  class DicomStructureSetLoader::RestInstanceLookupHandler::AddReferencedInstance : public LoaderStateMachine::State
   {
   private:
     std::string instanceId_;
@@ -71,27 +100,14 @@
       dicom.FromDicomAsJson(tags);
 
       DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
-
-      loader.content_->AddReferencedSlice(dicom);
-      loader.countProcessedInstances_ ++;
-      assert(loader.countProcessedInstances_ <= loader.countReferencedInstances_);
-
-      loader.revision_++;
-      loader.SetStructuresUpdated();
-
-      if (loader.countProcessedInstances_ == loader.countReferencedInstances_)
-      {
-        // All the referenced instances have been loaded, finalize the RT-STRUCT
-        loader.content_->CheckReferencedSlices();
-        loader.revision_++;
-        loader.SetStructuresReady();
-      }
+    
+      loader.AddReferencedSlice(dicom);
     }
   };
 
 
   // State that converts a "SOP Instance UID" to an Orthanc identifier
-  class DicomStructureSetLoader::LookupInstance : public LoaderStateMachine::State
+  class DicomStructureSetLoader::RestInstanceLookupHandler::LookupInstance : public LoaderStateMachine::State
   {
   private:
     std::string  sopInstanceUid_;
@@ -106,9 +122,6 @@
 
     virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
     {
-#if 0
-      LOG(TRACE) << "DicomStructureSetLoader::LookupInstance::Handle() (SUCCESS)";
-#endif
       DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
 
       Json::Value lookup;
@@ -147,6 +160,21 @@
     }
   };
 
+  void DicomStructureSetLoader::RestInstanceLookupHandler::RetrieveReferencedSlices(
+    const std::set<std::string>& nonEmptyInstances)
+  {
+    for (std::set<std::string>::const_iterator it = nonEmptyInstances.begin(); 
+         it != nonEmptyInstances.end(); 
+         ++it)
+    {
+      std::unique_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
+      command->SetUri("/tools/lookup");
+      command->SetMethod(Orthanc::HttpMethod_Post);
+      command->SetBody(*it);
+      command->AcquirePayload(new LookupInstance(loader_, *it));
+      Schedule(command.release());
+    }
+  }
 
   class DicomStructureSetLoader::LoadStructure : public LoaderStateMachine::State
   {
@@ -159,53 +187,28 @@
     virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
     {
       DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
-        
+
+      // Set the actual structure set content
       {
         OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer());
+
         loader.content_.reset(new OrthancStone::DicomStructureSet(dicom));
-        size_t structureCount = loader.content_->GetStructuresCount();
-        loader.structureVisibility_.resize(structureCount);
-        bool everythingVisible = false;
-        if ((loader.initiallyVisibleStructures_.size() == 1)
-          && (loader.initiallyVisibleStructures_[0].size() == 1)
-          && (loader.initiallyVisibleStructures_[0][0] == '*'))
-        {
-          everythingVisible = true;
-        }
-
-        for (size_t i = 0; i < structureCount; ++i)
-        {
-          // if a single "*" string is supplied, this means we want everything 
-          // to be visible...
-          if(everythingVisible)
-          {
-            loader.structureVisibility_.at(i) = true;
-          }
-          else
-          {
-            // otherwise, we only enable visibility for those structures whose 
-            // names are mentioned in the initiallyVisibleStructures_ array
-            const std::string& structureName = loader.content_->GetStructureName(i);
-
-            std::vector<std::string>::iterator foundIt =
-              std::find(
-                loader.initiallyVisibleStructures_.begin(),
-                loader.initiallyVisibleStructures_.end(),
-                structureName);
-            std::vector<std::string>::iterator endIt = loader.initiallyVisibleStructures_.end();
-            if (foundIt != endIt)
-              loader.structureVisibility_.at(i) = true;
-            else
-              loader.structureVisibility_.at(i) = false;
-          }
-        }
       }
 
+      // initialize visibility flags
+      SetDefaultStructureVisibility();
+
+      // retrieve the (non-empty) referenced instances (the CT slices containing the corresponding structures)
       // Some (admittedly invalid) Dicom files have empty values in the 
       // 0008,1155 tag. We try our best to cope with this.
+      // this is why we use `nonEmptyInstances` and not `instances`
       std::set<std::string> instances;
       std::set<std::string> nonEmptyInstances;
+
+      // this traverses the polygon collection for all structures and retrieve the SOPInstanceUID of 
+      // the referenced instances
       loader.content_->GetReferencedInstances(instances);
+
       for (std::set<std::string>::const_iterator
         it = instances.begin(); it != instances.end(); ++it)
       {
@@ -214,20 +217,55 @@
           nonEmptyInstances.insert(instance);
       }
 
-      loader.countReferencedInstances_ = 
-        static_cast<unsigned int>(nonEmptyInstances.size());
+      loader.RetrieveReferencedSlices(nonEmptyInstances);
+    }
+
+    void SetDefaultStructureVisibility()
+    {
+      DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
+
+      size_t structureCount = loader.content_->GetStructuresCount();
 
-      for (std::set<std::string>::const_iterator
-        it = nonEmptyInstances.begin(); it != nonEmptyInstances.end(); ++it)
+      loader.structureVisibility_.resize(structureCount);
+      bool everythingVisible = false;
+      if ((loader.initiallyVisibleStructures_.size() == 1)
+          && (loader.initiallyVisibleStructures_[0].size() == 1)
+          && (loader.initiallyVisibleStructures_[0][0] == '*'))
+      {
+        everythingVisible = true;
+      }
+
+      for (size_t i = 0; i < structureCount; ++i)
       {
-        std::unique_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
-        command->SetUri("/tools/lookup");
-        command->SetMethod(Orthanc::HttpMethod_Post);
-        command->SetBody(*it);
-        command->AcquirePayload(new LookupInstance(loader, *it));
-        Schedule(command.release());
+        // if a single "*" string is supplied, this means we want everything 
+        // to be visible...
+        if (everythingVisible)
+        {
+          loader.structureVisibility_.at(i) = true;
+        }
+        else
+        {
+          // otherwise, we only enable visibility for those structures whose 
+          // names are mentioned in the initiallyVisibleStructures_ array
+          const std::string& structureName = loader.content_->GetStructureName(i);
+
+          std::vector<std::string>::iterator foundIt =
+            std::find(
+              loader.initiallyVisibleStructures_.begin(),
+              loader.initiallyVisibleStructures_.end(),
+              structureName);
+          std::vector<std::string>::iterator endIt = loader.initiallyVisibleStructures_.end();
+          if (foundIt != endIt)
+            loader.structureVisibility_.at(i) = true;
+          else
+            loader.structureVisibility_.at(i) = false;
+        }
       }
     }
+
+    private:
+
+
   };
     
 
@@ -347,8 +385,9 @@
     , countReferencedInstances_(0)
     , structuresReady_(false)
   {
+    // the default handler to retrieve slice geometry is RestInstanceLookupHandler
+    instanceLookupHandler_ = RestInstanceLookupHandler::Create(*this);
   }
-   
     
   boost::shared_ptr<OrthancStone::DicomStructureSetLoader> DicomStructureSetLoader::Create(OrthancStone::ILoadersContext& loadersContext)
   {
@@ -357,7 +396,31 @@
         loadersContext));
     obj->LoaderStateMachine::PostConstructor();
     return obj;
+  }
 
+  void DicomStructureSetLoader::AddReferencedSlice(const Orthanc::DicomMap& dicom)
+  {
+    content_->AddReferencedSlice(dicom);
+    countProcessedInstances_ ++;
+    assert(countProcessedInstances_ <= countReferencedInstances_);
+
+    revision_++;
+    SetStructuresUpdated();
+
+    if (countProcessedInstances_ == countReferencedInstances_)
+    {
+      // All the referenced instances have been loaded, finalize the RT-STRUCT
+      content_->CheckReferencedSlices();
+      revision_++;
+      SetStructuresReady();
+    }
+  }
+
+  void DicomStructureSetLoader::RetrieveReferencedSlices(const std::set<std::string>& nonEmptyInstances)
+  {
+    // we set the number of referenced instances. This allows to know, in the method above, when we're done
+    countReferencedInstances_ = static_cast<unsigned int>(nonEmptyInstances.size());
+    instanceLookupHandler_->RetrieveReferencedSlices(nonEmptyInstances);
   }
 
   void DicomStructureSetLoader::SetStructureDisplayState(size_t structureIndex, bool display)
--- a/Framework/Loaders/DicomStructureSetLoader.h	Tue May 19 15:31:38 2020 +0200
+++ b/Framework/Loaders/DicomStructureSetLoader.h	Tue Jun 02 16:46:12 2020 +0200
@@ -35,12 +35,76 @@
     public OrthancStone::IVolumeSlicer,
     public OrthancStone::IObservable
   {
+  public:
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, StructuresReady, DicomStructureSetLoader);
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, StructuresUpdated, DicomStructureSetLoader);
+
+    /**
+    
+    Once the structure set has been loaded (the LoadStructure state), we need to fill it with geometry information
+    from the referenced slices (tag (0008,1155) described here:
+    https://dicom.innolitics.com/ciods/rt-structure-set/general-reference/00081140/00081155
+
+    This interface allows to customize how this information can be gathered. By default, the RestInstanceLookupHandler
+    will perform a REST call to the Orthanc API to retrieve this information.
+
+    Injecting another implementation of this interface is useful when where this information can be supplied in 
+    another (faster) way (for instance, if a separate loader for the CT series can be used to supply the slice geometry)
+    */
+    class IInstanceLookupHandler
+    {
+    public:
+      virtual void RetrieveReferencedSlices(const std::set<std::string>& instances) = 0;
+    };
+
+    // predeclaration of the default IInstanceLookupHandler implementation
+    class RestInstanceLookupHandler;
+
+    static boost::shared_ptr<DicomStructureSetLoader> Create(
+      OrthancStone::ILoadersContext& loadersContext);
+
+    void SetInstanceLookupHandler(boost::shared_ptr<IInstanceLookupHandler> instanceLookupHandler)
+    {
+      instanceLookupHandler_ = instanceLookupHandler;
+    }
+
+    OrthancStone::DicomStructureSet* GetContent()
+    {
+      return content_.get();
+    }
+
+    void SetStructureDisplayState(size_t structureIndex, bool display);
+    
+    bool GetStructureDisplayState(size_t structureIndex) const
+    {
+      return structureVisibility_.at(structureIndex);
+    }
+
+    ~DicomStructureSetLoader();
+    
+    void LoadInstance(const std::string& instanceId, 
+                      const std::vector<std::string>& initiallyVisibleStructures = std::vector<std::string>());
+
+    void LoadInstanceFullVisibility(const std::string& instanceId);
+
+
+    virtual IExtractedSlice* ExtractSlice(const OrthancStone::CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE;
+
+    void SetStructuresReady();
+    void SetStructuresUpdated();
+
+    bool AreStructuresReady() const;
+
+    /**
+      Called by the IInstanceLookupHandler when slice referenced instance information is available. 
+      When the last referenced slice is received, this method will perform a final check and will warn observers
+    */
+    void AddReferencedSlice(const Orthanc::DicomMap& dicom);
+
   private:
     class Slice;
 
-    // States of LoaderStateMachine
-    class AddReferencedInstance;   // 3rd state
-    class LookupInstance;          // 2nd state
+    // Only state of LoaderStateMachine
     class LoadStructure;           // 1st state
     
     OrthancStone::ILoadersContext&                    loadersContext_;
@@ -70,41 +134,13 @@
     */
     std::vector<bool>                  structureVisibility_;
 
+
+    boost::shared_ptr<IInstanceLookupHandler> instanceLookupHandler_;
+
+  private:
+    void RetrieveReferencedSlices(const std::set<std::string>& nonEmptyInstances);
+
   protected:
     DicomStructureSetLoader(OrthancStone::ILoadersContext& loadersContext);
-
-  public:
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, StructuresReady, DicomStructureSetLoader);
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, StructuresUpdated, DicomStructureSetLoader);
-
-    static boost::shared_ptr<DicomStructureSetLoader> Create(
-      OrthancStone::ILoadersContext& loadersContext);
-
-    OrthancStone::DicomStructureSet* GetContent()
-    {
-      return content_.get();
-    }
-
-    void SetStructureDisplayState(size_t structureIndex, bool display);
-    
-    bool GetStructureDisplayState(size_t structureIndex) const
-    {
-      return structureVisibility_.at(structureIndex);
-    }
-
-    ~DicomStructureSetLoader();
-    
-    void LoadInstance(const std::string& instanceId, 
-                      const std::vector<std::string>& initiallyVisibleStructures = std::vector<std::string>());
-
-    void LoadInstanceFullVisibility(const std::string& instanceId);
-
-
-    virtual IExtractedSlice* ExtractSlice(const OrthancStone::CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE;
-
-    void SetStructuresReady();
-    void SetStructuresUpdated();
-
-    bool AreStructuresReady() const;
   };
 }
--- a/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp	Tue May 19 15:31:38 2020 +0200
+++ b/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp	Tue Jun 02 16:46:12 2020 +0200
@@ -322,7 +322,7 @@
       {
         std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(loadersContext_.Lock());
         boost::shared_ptr<IObserver> observer(GetSharedObserver());
-        lock->Schedule(observer, 0, command.release()); // TODO: priority!
+        lock->Schedule(observer, sliceSchedulingPriority_, command.release());
       }
     }
     else
@@ -362,6 +362,9 @@
         // the 3D plane corresponding to the slice
         OrthancStone::CoordinateSystem3D geometry = instance->GetGeometry();
         slices.AddSlice(geometry, instance.release());
+
+        if (slicePostProcessor_)
+          slicePostProcessor_->ProcessCTDicomSlice(dicom);
       }
 
       seriesGeometry_.ComputeGeometry(slices);
@@ -476,6 +479,33 @@
     SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), quality);
   }
 
+
+  void  OrthancSeriesVolumeProgressiveLoader::SetMetadataSchedulingPriority(int p)
+  {
+    medadataSchedulingPriority_ = p;
+  }
+
+  int   OrthancSeriesVolumeProgressiveLoader::GetMetadataSchedulingPriority() const
+  {
+    return medadataSchedulingPriority_;
+  }
+
+  void  OrthancSeriesVolumeProgressiveLoader::SetSliceSchedulingPriority(int p)
+  {
+    sliceSchedulingPriority_ = p;
+  }
+    
+  int   OrthancSeriesVolumeProgressiveLoader::GetSliceSchedulingPriority() const
+  {
+    return sliceSchedulingPriority_;
+  }
+
+  void  OrthancSeriesVolumeProgressiveLoader::SetSchedulingPriority(int p)
+  {
+    medadataSchedulingPriority_ = p;
+    sliceSchedulingPriority_ = p;
+  }
+
   OrthancSeriesVolumeProgressiveLoader::OrthancSeriesVolumeProgressiveLoader(
     OrthancStone::ILoadersContext& loadersContext,
     boost::shared_ptr<OrthancStone::DicomVolumeImage> volume,
@@ -487,6 +517,8 @@
     , volume_(volume)
     , sorter_(new OrthancStone::BasicFetchingItemsSorter::Factory)
     , volumeImageReadyInHighQuality_(false)
+    , medadataSchedulingPriority_(0)
+    , sliceSchedulingPriority_(0)
   {
   }
 
@@ -557,7 +589,7 @@
       {
         std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(loadersContext_.Lock());
         boost::shared_ptr<IObserver> observer(GetSharedObserver());
-        lock->Schedule(observer, 0, command.release()); //TODO: priority!
+        lock->Schedule(observer, medadataSchedulingPriority_, command.release());
       }
     }
   }
--- a/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h	Tue May 19 15:31:38 2020 +0200
+++ b/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h	Tue Jun 02 16:46:12 2020 +0200
@@ -51,13 +51,21 @@
     public OrthancStone::IVolumeSlicer,
     public IGeometryProvider
   {
+  public:
+    class ISlicePostProcessor
+    {
+    public:
+      virtual void ProcessCTDicomSlice(const Orthanc::DicomMap& dicom) = 0;
+    };
+
   private:
     static const unsigned int QUALITY_00 = 0;
     static const unsigned int QUALITY_01 = 1;
     static const unsigned int QUALITY_02 = 2;
         
     class ExtractedSlice;
-    
+
+   
     /** Helper class internal to OrthancSeriesVolumeProgressiveLoader */
     class SeriesGeometry : public boost::noncopyable
     {
@@ -121,6 +129,14 @@
     std::vector<unsigned int>     slicesQuality_;
     bool                          volumeImageReadyInHighQuality_;
     
+    boost::shared_ptr<ISlicePostProcessor>  slicePostProcessor_;
+
+    /** See priority setters/getters below */
+    int medadataSchedulingPriority_;
+
+    /** See priority setters/getters below */
+    int sliceSchedulingPriority_;
+
     OrthancSeriesVolumeProgressiveLoader(
       OrthancStone::ILoadersContext& loadersContext,
       boost::shared_ptr<OrthancStone::DicomVolumeImage> volume,
@@ -141,6 +157,40 @@
 
     void SetSimultaneousDownloads(unsigned int count);
 
+    /**
+      Sets the relative priority of the requests for metadata.
+      - if p < PRIORITY_HIGH (-1)                 , the requests will be high priority
+      - if PRIORITY_LOW (100) > p > PRIORITY_HIGH , the requests will be medium priority
+      - if p > PRIORITY_LOW                       , the requests will be low priority
+
+      Default is 0 (medium)
+    */
+    void  SetMetadataSchedulingPriority(int p);
+
+    /** @see SetMetadataSchedulingPriority */
+    int   GetMetadataSchedulingPriority() const;
+
+    /** Same as SetMetadataSchedulingPriority, for slices. Default is 0. */
+    void  SetSliceSchedulingPriority(int p);
+    
+    /** @see SetSliceSchedulingPriority */
+    int   GetSliceSchedulingPriority() const;
+
+    /** Sets priorities for all requests. @see SetMetadataSchedulingPriority */
+    void  SetSchedulingPriority(int p);
+
+    void SetDicomSlicePostProcessor(boost::shared_ptr<ISlicePostProcessor> slicePostProcessor)
+    {
+      // this will delete the previously stored slice processor, if any
+      slicePostProcessor_ = slicePostProcessor;
+    }
+
+    boost::shared_ptr<ISlicePostProcessor> GetDicomSlicePostProcessor()
+    {
+      // this could be empty!
+      return slicePostProcessor_;
+    }
+
     bool IsVolumeImageReadyInHighQuality() const
     {
       return volumeImageReadyInHighQuality_;
--- a/Samples/Common/RtViewerApp.cpp	Tue May 19 15:31:38 2020 +0200
+++ b/Samples/Common/RtViewerApp.cpp	Tue Jun 02 16:46:12 2020 +0200
@@ -119,6 +119,10 @@
       // "false" means only using hi quality
       // TODO: add flag for quality
       ctLoader_ = OrthancSeriesVolumeProgressiveLoader::Create(*loadersContext_, ctVolume_, true);
+      
+      // better priority for CT vs dose and struct
+      ctLoader_->SetSchedulingPriority(-100);
+
 
       // we need to store the CT loader to ask from geometry details later on when geometry is loaded
       geometryProvider_ = ctLoader_;
--- a/Samples/Sdl/SdlHelpers.h	Tue May 19 15:31:38 2020 +0200
+++ b/Samples/Sdl/SdlHelpers.h	Tue Jun 02 16:46:12 2020 +0200
@@ -94,9 +94,9 @@
     }
 
     p.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y));
-    p.SetAltModifier(modifiers & KeyboardModifiers_Alt);
-    p.SetControlModifier(modifiers & KeyboardModifiers_Control);
-    p.SetShiftModifier(modifiers & KeyboardModifiers_Shift);
+    p.SetAltModifier( (modifiers & KeyboardModifiers_Alt) != 0);
+    p.SetControlModifier( (modifiers & KeyboardModifiers_Control) != 0);
+    p.SetShiftModifier( (modifiers & KeyboardModifiers_Shift) != 0);
   }
 
   static boost::shared_ptr<OrthancStone::SdlViewport> GetSdlViewportFromWindowId(
--- a/UnitTestsSources/TestStructureSet.cpp	Tue May 19 15:31:38 2020 +0200
+++ b/UnitTestsSources/TestStructureSet.cpp	Tue Jun 02 16:46:12 2020 +0200
@@ -32,6 +32,12 @@
 #include "Framework/Toolbox/DicomStructureSet2.h"
 #include "Framework/Toolbox/DisjointDataSet.h"
 
+#include "Framework/Loaders/GenericLoadersContext.h"
+#include "Framework/Loaders/DicomStructureSetLoader.h"
+#include "Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h"
+
+#include "boost/date_time/posix_time/posix_time.hpp"
+
 #include <Core/SystemToolbox.h>
 
 #include "gtest/gtest.h"
@@ -5406,9 +5412,359 @@
   const std::vector<DicomStructure2>& structures = structureSet.structures_;
 }
 
-
 #endif 
 // BGO_ENABLE_DICOMSTRUCTURESETLOADER2
 
-
-
+namespace
+{
+  void Initialize(const char* orthancApiUrl, OrthancStone::ILoadersContext& loadersContext)
+  {
+    Orthanc::WebServiceParameters p;
+
+    OrthancStone::GenericLoadersContext& typedLoadersContext =
+      dynamic_cast<OrthancStone::GenericLoadersContext&>(loadersContext);
+    // Default is http://localhost:8042
+    // Here's how you may change it
+    p.SetUrl(orthancApiUrl);
+    p.SetCredentials("orthanc", "orthanc");
+    typedLoadersContext.SetOrthancParameters(p);
+
+    typedLoadersContext.StartOracle();
+  }
+
+  void Exitialize(OrthancStone::ILoadersContext& loadersContext)
+  {
+    OrthancStone::GenericLoadersContext& typedLoadersContext =
+      dynamic_cast<OrthancStone::GenericLoadersContext&>(loadersContext);
+
+    typedLoadersContext.StopOracle();
+  }
+
+
+#if 0
+  class TestObserver : public ObserverBase<TestObserver>
+  {
+  public:
+    TestObserver() {};
+
+    virtual void Handle
+
+  };
+#endif
+
+}
+
+TEST(StructureSet, DISABLED_StructureSetLoader_injection_feature_2020_05_10)
+{
+  namespace pt = boost::posix_time;
+
+  std::unique_ptr<OrthancStone::ILoadersContext> loadersContext(new OrthancStone::GenericLoadersContext(1,4,1));
+  Initialize("http://localhost:8042/", *loadersContext);
+
+  boost::shared_ptr<DicomStructureSetLoader> loader = DicomStructureSetLoader::Create(*loadersContext);
+
+  // replace with Orthanc ID of an uploaded RTSTRUCT instance!
+  loader->LoadInstanceFullVisibility("72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661");
+
+  bool bContinue(true);
+
+  pt::ptime initialTime = pt::second_clock::local_time();
+
+  while (bContinue)
+  {
+    bContinue = !loader->AreStructuresReady();
+    boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
+
+    {
+      pt::ptime nowTime = pt::second_clock::local_time();
+      pt::time_duration diff = nowTime - initialTime;
+      double seconds = static_cast<double>(diff.total_milliseconds()) * 0.001;
+      std::cout << seconds << " seconds elapsed...\n";
+      if (seconds > 30)
+      {
+        std::cout << "More than 30 seconds elapsed... Aborting test :(\n";
+        //GTEST_FATAL_FAILURE_("More than 30 seconds elapsed... Aborting test :(");
+        //bContinue = false;
+      }
+    }
+  }
+}
+
+class SliceProcessor :
+  public OrthancStone::OrthancSeriesVolumeProgressiveLoader::ISlicePostProcessor,
+  public OrthancStone::DicomStructureSetLoader::IInstanceLookupHandler
+{
+public:
+  SliceProcessor(OrthancStone::DicomStructureSetLoader& structLoader) : structLoader_(structLoader)
+  {
+  }
+
+  virtual void ProcessCTDicomSlice(const Orthanc::DicomMap& instance) ORTHANC_OVERRIDE
+  {
+    std::string sopInstanceUid;
+    if (!instance.LookupStringValue(sopInstanceUid, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Missing SOPInstanceUID in a DICOM instance");
+    }
+    slicesDicom_[sopInstanceUid] = boost::shared_ptr<DicomMap>(instance.Clone());
+  }
+
+  virtual void RetrieveReferencedSlices(const std::set<std::string>& nonEmptyInstances) ORTHANC_OVERRIDE
+  {
+    for (std::set<std::string>::const_iterator it = nonEmptyInstances.begin(); 
+         it != nonEmptyInstances.end(); 
+         ++it)
+    {
+      const std::string nonEmptyInstance = *it;
+      if (slicesDicom_.find(nonEmptyInstance) == slicesDicom_.end())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Referenced SOPInstanceUID not found in CT");
+      }
+      boost::shared_ptr<Orthanc::DicomMap> instance = slicesDicom_[nonEmptyInstance];
+      structLoader_.AddReferencedSlice(*instance);
+    }
+  }
+
+  OrthancStone::DicomStructureSetLoader& structLoader_;
+  std::map<std::string, boost::shared_ptr<Orthanc::DicomMap> > slicesDicom_;
+};
+
+void LoadCtSeriesBlocking(boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader, std::string seriesId)
+{
+  namespace pt = boost::posix_time;
+  
+  // Load the CT 
+  ctLoader->LoadSeries(seriesId);
+
+  // Wait for CT to be loaded
+  pt::ptime initialTime = pt::second_clock::local_time();
+  {
+    bool bContinue(true);
+    while (bContinue)
+    {
+      bContinue = !ctLoader->IsVolumeImageReadyInHighQuality();
+      boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
+
+      {
+        pt::ptime nowTime = pt::second_clock::local_time();
+        pt::time_duration diff = nowTime - initialTime;
+        double seconds = static_cast<double>(diff.total_milliseconds()) * 0.001;
+        std::cout << seconds << " seconds elapsed...\n";
+        if (seconds > 30)
+        {
+          const char* msg = "More than 30 seconds elapsed when waiting for CT... Aborting test :(\n";
+          GTEST_FATAL_FAILURE_(msg);
+          bContinue = false;
+        }
+      }
+    }
+  }
+}
+
+
+/**
+Will fill planes
+*/
+void GetCTPlanes(std::vector<OrthancStone::CoordinateSystem3D>& planes, 
+                 OrthancStone::VolumeProjection projection,
+                 boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader)
+{
+    planes.clear(); // inefficient : we don't care
+
+    const VolumeImageGeometry& geometry = ctLoader->GetImageGeometry();
+    const unsigned int depth = geometry.GetProjectionDepth(projection);
+
+    planes.resize(depth);
+
+    for (unsigned int z = 0; z < depth; z++)
+    {
+      planes[z] = geometry.GetProjectionSlice(projection, z);
+    }
+}
+
+void LoadRtStructBlocking(boost::shared_ptr<OrthancStone::DicomStructureSetLoader> structLoader, std::string instanceId)
+{
+  namespace pt = boost::posix_time;
+
+  // Load RTSTRUCT
+  structLoader->LoadInstanceFullVisibility(instanceId);
+
+  pt::ptime initialTime = pt::second_clock::local_time();
+
+  // Wait for the loading process to complete
+  {
+    bool bContinue(true);
+    while (bContinue)
+    {
+      bContinue = !structLoader->AreStructuresReady();
+      boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
+
+      {
+        pt::ptime nowTime = pt::second_clock::local_time();
+        pt::time_duration diff = nowTime - initialTime;
+        double seconds = static_cast<double>(diff.total_milliseconds()) * 0.001;
+        std::cout << seconds << " seconds elapsed...\n";
+        if (seconds > 30)
+        {
+          const char* msg = "More than 30 seconds elapsed when waiting for RTSTRUCT... Aborting test :(\n";
+          GTEST_FATAL_FAILURE_(msg);
+          bContinue = false;
+        }
+      }
+    }
+  }
+}
+
+TEST(StructureSet, DISABLED_Integration_Compound_CT_Struct_Loading)
+{
+  const double TOLERANCE = 0.0000001;
+
+  // create loaders context
+  std::unique_ptr<OrthancStone::ILoadersContext> loadersContext(new OrthancStone::GenericLoadersContext(1,4,1));
+  Initialize("http://localhost:8042/", *loadersContext);
+
+  const char* ctSeriesId = "a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa";
+  const char* rtStructInstanceId = "54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9";
+
+  // we'll compare normal loading and optimized loading with SliceProcessor to store the dicom
+
+  boost::shared_ptr<OrthancStone::DicomStructureSetLoader> normalStructLoader;
+  boost::shared_ptr<OrthancStone::DicomStructureSetLoader> optimizedStructLoader;
+  
+  {
+    // Create the CT volume
+    boost::shared_ptr<OrthancStone::DicomVolumeImage> volume = boost::make_shared<OrthancStone::DicomVolumeImage>();
+
+    // Create CT loader
+    boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader =
+      OrthancStone::OrthancSeriesVolumeProgressiveLoader::Create(*loadersContext, volume);
+
+    // Create struct loader
+    normalStructLoader = OrthancStone::DicomStructureSetLoader::Create(*loadersContext);
+
+    // Load the CT
+    LoadCtSeriesBlocking(ctLoader, ctSeriesId);
+    
+    const OrthancStone::VolumeImageGeometry& imageGeometry = ctLoader->GetImageGeometry();
+    unsigned int width = imageGeometry.GetWidth();
+    EXPECT_EQ(512u, width);
+    unsigned int height = imageGeometry.GetHeight();
+    EXPECT_EQ(512u, height);
+    unsigned int depth = imageGeometry.GetDepth();
+    EXPECT_EQ(109u, depth);
+
+    // Load the RTStruct
+    LoadRtStructBlocking(normalStructLoader, rtStructInstanceId);
+  }
+
+  std::vector<OrthancStone::CoordinateSystem3D> axialPlanes;
+  std::vector<OrthancStone::CoordinateSystem3D> coronalPlanes;
+  std::vector<OrthancStone::CoordinateSystem3D> sagittalPlanes;
+
+  {
+    // Create the CT volume
+    boost::shared_ptr<OrthancStone::DicomVolumeImage> volume = boost::make_shared<OrthancStone::DicomVolumeImage>();
+
+    // Create CT loader
+    boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader =
+      OrthancStone::OrthancSeriesVolumeProgressiveLoader::Create(*loadersContext, volume);
+
+    // Create struct loader
+    optimizedStructLoader = OrthancStone::DicomStructureSetLoader::Create(*loadersContext);
+
+    // create the slice processor / instance lookup
+    boost::shared_ptr<SliceProcessor> sliceProcessor(new SliceProcessor(*optimizedStructLoader));
+
+    // Inject it into CT loader
+    ctLoader->SetDicomSlicePostProcessor(sliceProcessor);
+
+    // Inject it into RTSTRUCT loader
+    optimizedStructLoader->SetInstanceLookupHandler(sliceProcessor);
+
+    // Load the CT
+    LoadCtSeriesBlocking(ctLoader, ctSeriesId);
+  
+    // now, the slices are collected. let's do some checks
+    EXPECT_EQ(109u, sliceProcessor->slicesDicom_.size());
+
+    // Load the RTStruct
+    LoadRtStructBlocking(optimizedStructLoader, rtStructInstanceId);
+
+    GetCTPlanes(axialPlanes, VolumeProjection_Axial, ctLoader);
+    GetCTPlanes(coronalPlanes, VolumeProjection_Coronal, ctLoader);
+    GetCTPlanes(sagittalPlanes, VolumeProjection_Sagittal, ctLoader);
+  }
+
+  // DO NOT DELETE THOSE!
+  OrthancStone::DicomStructureSet* normalContent = normalStructLoader->GetContent();
+  OrthancStone::DicomStructureSet* optimizedContent = optimizedStructLoader->GetContent();
+
+  EXPECT_EQ(normalContent->GetStructuresCount(), optimizedContent->GetStructuresCount());
+
+  /*void GetCTPlanes(std::vector<OrthancStone::CoordinateSystem3D>& planes, 
+                 OrthancStone::VolumeProjection projection,
+                 boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader)*/
+
+
+  std::vector<OrthancStone::CoordinateSystem3D> allPlanes;
+
+  // let's gather all the possible cutting planes in a single struct
+  for (size_t i = 0; i < axialPlanes.size(); ++i)
+    allPlanes.push_back(axialPlanes[i]);
+
+  for (size_t i = 0; i < coronalPlanes.size(); ++i)
+    allPlanes.push_back(coronalPlanes[i]);
+
+  for (size_t i = 0; i < sagittalPlanes.size(); ++i)
+    allPlanes.push_back(sagittalPlanes[i]);
+
+  for (size_t i = 0; i < normalContent->GetStructuresCount(); ++i)
+  {
+    std::cout << "Testing structure (" << i << "/" << normalContent->GetStructuresCount() << ")\n";
+    Vector structureCenter1                     = normalContent->GetStructureCenter(i);
+    const std::string& structureName1           = normalContent->GetStructureName(i);
+    const std::string& structureInterpretation1 = normalContent->GetStructureInterpretation(i);
+    Color structureColor1                       = normalContent->GetStructureColor(i);
+
+    Vector structureCenter2                     = optimizedContent->GetStructureCenter(i);
+    const std::string& structureName2           = optimizedContent->GetStructureName(i);
+    const std::string& structureInterpretation2 = optimizedContent->GetStructureInterpretation(i);
+    Color structureColor2                       = optimizedContent->GetStructureColor(i);
+
+    EXPECT_NEAR(structureCenter1[0], structureCenter2[0], TOLERANCE);
+    EXPECT_NEAR(structureCenter1[1], structureCenter2[1], TOLERANCE);
+    EXPECT_NEAR(structureCenter1[2], structureCenter2[2], TOLERANCE);
+    
+    EXPECT_EQ(structureName1, structureName2);
+    EXPECT_EQ(structureInterpretation1, structureInterpretation2);
+    EXPECT_EQ(structureColor1.GetRed(), structureColor2.GetRed());
+    EXPECT_EQ(structureColor1.GetGreen(), structureColor2.GetGreen());
+    EXPECT_EQ(structureColor1.GetBlue(), structureColor2.GetBlue());
+
+    // "random" walk through the planes. Processing them all takes too long (~ 1 min)
+    for (size_t j = 0; j < allPlanes.size(); j += 37)
+    {
+      const OrthancStone::CoordinateSystem3D& plane = allPlanes[j];
+
+      std::vector< std::pair<Point2D, Point2D> > segments1;
+      std::vector< std::pair<Point2D, Point2D> > segments2;
+      
+      bool ok1 = normalContent->ProjectStructure(segments1, i, plane);
+      bool ok2 = optimizedContent->ProjectStructure(segments2, i, plane);
+
+      // checks here
+      EXPECT_EQ(ok1, ok2);
+      EXPECT_EQ(segments1.size(), segments2.size());
+
+      for (size_t k = 0; k < segments1.size(); ++k)
+      {
+        EXPECT_NEAR(segments1[k].first.x, segments2[k].first.x, TOLERANCE);
+        EXPECT_NEAR(segments1[k].first.y, segments2[k].first.y, TOLERANCE);
+        EXPECT_NEAR(segments1[k].second.x, segments2[k].second.x, TOLERANCE);
+        EXPECT_NEAR(segments1[k].second.y, segments2[k].second.y, TOLERANCE);
+      }
+    }
+  }
+
+  Exitialize(*loadersContext);
+}