changeset 1703:b80e76dd1d56 db-changes

ordered-slices continued
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 13 Oct 2015 16:10:35 +0200
parents 9980875edc7c
children d2268c7a7ede
files Core/Enumerations.cpp Core/Enumerations.h NEWS OrthancServer/OrthancRestApi/OrthancRestResources.cpp OrthancServer/SliceOrdering.cpp OrthancServer/SliceOrdering.h Plugins/Include/orthanc/OrthancCPlugin.h Resources/ErrorCodes.json
diffstat 8 files changed, 242 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/Core/Enumerations.cpp	Mon Oct 12 17:49:23 2015 +0200
+++ b/Core/Enumerations.cpp	Tue Oct 13 16:10:35 2015 +0200
@@ -44,11 +44,6 @@
   // "Resources/GenerateErrorCodes.py"
   const char* EnumerationToString(ErrorCode error)
   {
-    if (error >= ErrorCode_START_PLUGINS)
-    {
-      return "Error encountered within some plugin";
-    }
-
     switch (error)
     {
       case ErrorCode_InternalError:
@@ -318,8 +313,21 @@
       case ErrorCode_DatabaseNotInitialized:
         return "Plugin trying to call the database during its initialization";
 
+      case ErrorCode_SslDisabled:
+        return "Orthanc has been built without SSL support";
+
+      case ErrorCode_CannotOrderSlices:
+        return "Unable to order the slices of the series";
+
       default:
-        return "Unknown error code";
+        if (error >= ErrorCode_START_PLUGINS)
+        {
+          return "Error encountered within some plugin";
+        }
+        else
+        {
+          return "Unknown error code";
+        }
     }
   }
 
--- a/Core/Enumerations.h	Mon Oct 12 17:49:23 2015 +0200
+++ b/Core/Enumerations.h	Tue Oct 13 16:10:35 2015 +0200
@@ -135,6 +135,7 @@
     ErrorCode_DatabaseBackendAlreadyRegistered = 2037    /*!< Another plugin has already registered a custom database back-end */,
     ErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
     ErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
+    ErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
     ErrorCode_START_PLUGINS = 1000000
   };
 
--- a/NEWS	Mon Oct 12 17:49:23 2015 +0200
+++ b/NEWS	Tue Oct 13 16:10:35 2015 +0200
@@ -5,7 +5,7 @@
 * "/tools/create-dicom": Support of binary tags encoded using data URI scheme
 * "/tools/create-dicom": Support of hierarchical structures (creation of sequences)
 * "/modify" can insert/modify sequences
-* "/series/.../ordered-slices" to order the slices of a 2D+t, 3D or 3D+t image
+* "/series/.../ordered-slices" to order the slices of a 2D+t or 3D image
 * New URIs for attachments: ".../compress", ".../uncompress" and ".../is-compressed"
 
 Plugins
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Mon Oct 12 17:49:23 2015 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Tue Oct 13 16:10:35 2015 +0200
@@ -1101,6 +1101,9 @@
     ServerIndex& index = OrthancRestApi::GetIndex(call);
     SliceOrdering ordering(index, id);
 
+    Json::Value result;
+    ordering.Format(result);
+    call.GetOutput().AnswerJson(result);
   }
 
 
--- a/OrthancServer/SliceOrdering.cpp	Mon Oct 12 17:49:23 2015 +0200
+++ b/OrthancServer/SliceOrdering.cpp	Tue Oct 13 16:10:35 2015 +0200
@@ -33,9 +33,13 @@
 #include "PrecompiledHeadersServer.h"
 #include "SliceOrdering.h"
 
+#include "../Core/Logging.h"
 #include "../Core/Toolbox.h"
+#include "ServerEnumerations.h"
 
+#include <algorithm>
 #include <boost/lexical_cast.hpp>
+#include <boost/noncopyable.hpp>
 
 
 namespace Orthanc
@@ -89,23 +93,40 @@
   }
 
 
-  struct SliceOrdering::Instance
+  struct SliceOrdering::Instance : public boost::noncopyable
   {
+  private:
     std::string   instanceId_;
     bool          hasPosition_;
     Vector        position_;   
     bool          hasIndexInSeries_;
     size_t        indexInSeries_;
+    unsigned int  framesCount_;
 
+  public:
     Instance(ServerIndex& index,
-          const std::string& instanceId) :
-      instanceId_(instanceId)
+             const std::string& instanceId) :
+      instanceId_(instanceId),
+      framesCount_(1)
     {
       DicomMap instance;
       if (!index.GetMainDicomTags(instance, instanceId, ResourceType_Instance, ResourceType_Instance))
       {
         throw OrthancException(ErrorCode_UnknownResource);
       }
+
+      const DicomValue* frames = instance.TestAndGetValue(DICOM_TAG_NUMBER_OF_FRAMES);
+      if (frames != NULL &&
+          !frames->IsNull())
+      {
+        try
+        {
+          framesCount_ = boost::lexical_cast<unsigned int>(frames->AsString());
+        }
+        catch (boost::bad_lexical_cast&)
+        {
+        }
+      }
       
       std::vector<float> tmp;
       hasPosition_ = TokenizeVector(tmp, instance, DICOM_TAG_IMAGE_POSITION_PATIENT, 3);
@@ -132,9 +153,68 @@
       {
       }
     }
+
+    const std::string& GetIdentifier() const
+    {
+      return instanceId_;
+    }
+
+    bool HasPosition() const
+    {
+      return hasPosition_;
+    }
+
+    float ComputeRelativePosition(const Vector& normal) const
+    {
+      assert(HasPosition());
+      return (normal[0] * position_[0] + 
+              normal[1] * position_[1] +
+              normal[2] * position_[2]);
+    }
+
+    bool HasIndexInSeries() const
+    {
+      return hasIndexInSeries_;
+    }
+    
+    size_t GetIndexInSeries() const
+    {
+      assert(HasIndexInSeries());
+      return indexInSeries_;
+    }
+
+    unsigned int GetFramesCount() const
+    {
+      return framesCount_;
+    }
   };
 
 
+  class SliceOrdering::PositionComparator
+  {
+  private:
+    const Vector&  normal_;
+
+  public:
+    PositionComparator(const Vector& normal) : normal_(normal)
+    {
+    }
+    
+    int operator() (const Instance* a,
+                    const Instance* b) const
+    {
+      return a->ComputeRelativePosition(normal_) < b->ComputeRelativePosition(normal_);
+    }
+  };
+
+
+  bool SliceOrdering::IndexInSeriesComparator(const SliceOrdering::Instance* a,
+                                              const SliceOrdering::Instance* b)
+  {
+    return a->GetIndexInSeries() < b->GetIndexInSeries();
+  }  
+
+
   void SliceOrdering::ComputeNormal()
   {
     DicomMap series;
@@ -171,6 +251,12 @@
 
   bool SliceOrdering::SortUsingPositions()
   {
+    if (instances_.size() <= 1)
+    {
+      // One single instance: It is sorted by default
+      return true;
+    }
+
     if (!hasNormal_)
     {
       return false;
@@ -179,13 +265,60 @@
     for (size_t i = 0; i < instances_.size(); i++)
     {
       assert(instances_[i] != NULL);
-      if (!instances_[i]->hasPosition_)
+      if (!instances_[i]->HasPosition())
       {
         return false;
       }
     }
 
+    PositionComparator comparator(normal_);
+    std::sort(instances_.begin(), instances_.end(), comparator);
+
+    float a = instances_.front()->ComputeRelativePosition(normal_);
+    float b = instances_.back()->ComputeRelativePosition(normal_);
+
+    if (std::fabs(b - a) <= 10.0f * std::numeric_limits<float>::epsilon())
+    {
+      // Not enough difference between the minimum and maximum
+      // positions along the normal of the volume
+      return false;
+    }
+    else
+    {
+      // This is a 3D volume
+      isVolume_ = true;
+      return true;
+    }
+  }
+
+
+  bool SliceOrdering::SortUsingIndexInSeries()
+  {
+    if (instances_.size() <= 1)
+    {
+      // One single instance: It is sorted by default
+      return true;
+    }
+
+    for (size_t i = 0; i < instances_.size(); i++)
+    {
+      assert(instances_[i] != NULL);
+      if (!instances_[i]->HasIndexInSeries())
+      {
+        return false;
+      }
+    }
+
+    std::sort(instances_.begin(), instances_.end(), IndexInSeriesComparator);
     
+    for (size_t i = 1; i < instances_.size(); i++)
+    {
+      if (instances_[i - 1]->GetIndexInSeries() == instances_[i]->GetIndexInSeries())
+      {
+        // The current "IndexInSeries" occurs 2 times: Not a proper ordering
+        return false;
+      }
+    }
 
     return true;
   }
@@ -194,10 +327,18 @@
   SliceOrdering::SliceOrdering(ServerIndex& index,
                                const std::string& seriesId) :
     index_(index),
-    seriesId_(seriesId)
+    seriesId_(seriesId),
+    isVolume_(false)
   {
     ComputeNormal();
     CreateInstances();
+
+    if (!SortUsingPositions() &&
+        !SortUsingIndexInSeries())
+    {
+      LOG(ERROR) << "Unable to order the slices of the series " << seriesId;
+      throw OrthancException(ErrorCode_CannotOrderSlices);
+    }
   }
 
 
@@ -212,4 +353,57 @@
       }
     }
   }
+
+
+  const std::string& SliceOrdering::GetInstanceId(size_t index) const
+  {
+    if (index >= instances_.size())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return instances_[index]->GetIdentifier();
+    }
+  }
+
+
+  unsigned int SliceOrdering::GetFramesCount(size_t index) const
+  {
+    if (index >= instances_.size())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return instances_[index]->GetFramesCount();
+    }
+  }
+
+
+  void SliceOrdering::Format(Json::Value& result) const
+  {
+    result = Json::objectValue;
+    result["Type"] = (isVolume_ ? "Volume" : "Sequence");
+    
+    Json::Value tmp = Json::arrayValue;
+    for (size_t i = 0; i < GetInstancesCount(); i++)
+    {
+      tmp.append(GetBasePath(ResourceType_Instance, GetInstanceId(i)) + "/file");
+    }
+
+    result["Dicom"] = tmp;
+
+    tmp.clear();
+    for (size_t i = 0; i < GetInstancesCount(); i++)
+    {
+      std::string base = GetBasePath(ResourceType_Instance, GetInstanceId(i));
+      for (size_t j = 0; j < GetFramesCount(i); j++)
+      {
+        tmp.append(base + "/frames/" + boost::lexical_cast<std::string>(j));
+      }
+    }
+
+    result["Slices"] = tmp;
+  }
 }
--- a/OrthancServer/SliceOrdering.h	Mon Oct 12 17:49:23 2015 +0200
+++ b/OrthancServer/SliceOrdering.h	Tue Oct 13 16:10:35 2015 +0200
@@ -42,12 +42,17 @@
     typedef float Vector[3];
 
     struct Instance;
+    struct PositionComparator;
 
     ServerIndex&             index_;
     std::string              seriesId_;
     bool                     hasNormal_;
     Vector                   normal_;
     std::vector<Instance*>   instances_;
+    bool                     isVolume_;
+
+    static bool IndexInSeriesComparator(const SliceOrdering::Instance* a,
+                                        const SliceOrdering::Instance* b);
 
     void ComputeNormal();
 
@@ -55,10 +60,23 @@
 
     bool SortUsingPositions();
 
+    bool SortUsingIndexInSeries();
+
   public:
     SliceOrdering(ServerIndex& index,
                   const std::string& seriesId);
 
     ~SliceOrdering();
+
+    size_t  GetInstancesCount() const
+    {
+      return instances_.size();
+    }
+
+    const std::string& GetInstanceId(size_t index) const;
+
+    unsigned int GetFramesCount(size_t index) const;
+
+    void Format(Json::Value& result) const;
   };
 }
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Mon Oct 12 17:49:23 2015 +0200
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Tue Oct 13 16:10:35 2015 +0200
@@ -268,6 +268,7 @@
     OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037    /*!< Another plugin has already registered a custom database back-end */,
     OrthancPluginErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
     OrthancPluginErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
+    OrthancPluginErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
 
     _OrthancPluginErrorCode_INTERNAL = 0x7fffffff
   } OrthancPluginErrorCode;
--- a/Resources/ErrorCodes.json	Mon Oct 12 17:49:23 2015 +0200
+++ b/Resources/ErrorCodes.json	Tue Oct 13 16:10:35 2015 +0200
@@ -497,5 +497,10 @@
     "Code": 2039,
     "Name": "SslDisabled",
     "Description": "Orthanc has been built without SSL support"
+  },
+  {
+    "Code": 2040,
+    "Name": "CannotOrderSlices",
+    "Description": "Unable to order the slices of the series"
   }
 ]