changeset 2804:d88970f1ffbf

fix ordering of non-parallel slices + /tools/reconstruct
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 29 Aug 2018 13:24:28 +0200
parents 579acc5e5412
children e02af4ca8003
files Core/DicomFormat/DicomMap.cpp NEWS OrthancServer/OrthancRestApi/OrthancRestResources.cpp OrthancServer/SliceOrdering.cpp OrthancServer/SliceOrdering.h UnitTestsSources/DicomMapTests.cpp
diffstat 6 files changed, 118 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- a/Core/DicomFormat/DicomMap.cpp	Tue Aug 28 15:14:33 2018 +0200
+++ b/Core/DicomFormat/DicomMap.cpp	Wed Aug 29 13:24:28 2018 +0200
@@ -109,7 +109,16 @@
     DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER,
     DICOM_TAG_SOP_INSTANCE_UID,
     DICOM_TAG_IMAGE_POSITION_PATIENT,    // New in db v6
-    DICOM_TAG_IMAGE_COMMENTS             // New in db v6
+    DICOM_TAG_IMAGE_COMMENTS,            // New in db v6
+
+    /**
+     * Main DICOM tags that are not part of any release of the
+     * database schema yet, and that will be part of future db v7. In
+     * the meantime, the user must call "/tools/reconstruct" once to
+     * access these tags if the corresponding DICOM files where
+     * indexed in the database by an older version of Orthanc.
+     **/
+    DICOM_TAG_IMAGE_ORIENTATION_PATIENT  // New in Orthanc 1.4.2
   };
 
 
--- a/NEWS	Tue Aug 28 15:14:33 2018 +0200
+++ b/NEWS	Wed Aug 29 13:24:28 2018 +0200
@@ -5,6 +5,13 @@
 -------
 
 * "OrthancPeers" configuration option now allows to specify HTTP headers
+* Fix "/series/.../ordered-slices" in the presence of non-parallel slices
+
+REST API
+--------
+
+* "/tools/reconstruct" to reconstruct the main DICOM tags, the JSON summary and
+  the metadata of all the instances stored in Orthanc. This is a slow operation!
 
 Plugins
 -------
@@ -15,6 +22,7 @@
 Maintenance
 -----------
 
+* New main DICOM tag: "ImageOrientationPatient" at the instance level
 * New configuration option: "HttpVerbose" to debug outgoing HTTP connections
 * Fix incoming DICOM C-Store filtering for JPEG-LS transfer syntaxes
 * Fix OrthancPluginHttpClient() to return the HTTP status on errors
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Tue Aug 28 15:14:33 2018 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Wed Aug 29 13:24:28 2018 +0200
@@ -1549,6 +1549,23 @@
   }
 
 
+  static void ReconstructAllResources(RestApiPostCall& call)
+  {
+    ServerContext& context = OrthancRestApi::GetContext(call);
+
+    std::list<std::string> studies;
+    context.GetIndex().GetAllUuids(studies, ResourceType_Study);
+
+    for (std::list<std::string>::const_iterator 
+           study = studies.begin(); study != studies.end(); ++study)
+    {
+      ServerToolbox::ReconstructResource(context, *study);
+    }
+    
+    call.GetOutput().AnswerBuffer("", "text/plain");
+  }
+
+
   void OrthancRestApi::RegisterResources()
   {
     Register("/instances", ListResources<ResourceType_Instance>);
@@ -1654,5 +1671,6 @@
     Register("/studies/{id}/reconstruct", ReconstructResource<ResourceType_Study>);
     Register("/series/{id}/reconstruct", ReconstructResource<ResourceType_Series>);
     Register("/instances/{id}/reconstruct", ReconstructResource<ResourceType_Instance>);
+    Register("/tools/reconstruct", ReconstructAllResources);
   }
 }
--- a/OrthancServer/SliceOrdering.cpp	Tue Aug 28 15:14:33 2018 +0200
+++ b/OrthancServer/SliceOrdering.cpp	Wed Aug 29 13:24:28 2018 +0200
@@ -95,12 +95,69 @@
   }
 
 
+  static bool IsCloseToZero(double x)
+  {
+    return fabs(x) < 10.0 * std::numeric_limits<float>::epsilon();
+  }
+
+  
+  bool SliceOrdering::ComputeNormal(Vector& normal,
+                                    const DicomMap& dicom)
+  {
+    std::vector<float> cosines;
+
+    if (TokenizeVector(cosines, dicom, DICOM_TAG_IMAGE_ORIENTATION_PATIENT, 6))
+    {
+      assert(cosines.size() == 6);
+      normal[0] = cosines[1] * cosines[5] - cosines[2] * cosines[4];
+      normal[1] = cosines[2] * cosines[3] - cosines[0] * cosines[5];
+      normal[2] = cosines[0] * cosines[4] - cosines[1] * cosines[3];
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool SliceOrdering::IsParallelOrOpposite(const Vector& u,
+                                           const Vector& v)
+  {
+    // Check out "GeometryToolbox::IsParallelOrOpposite()" in Stone of
+    // Orthanc for explanations
+    const double u1 = u[0];
+    const double u2 = u[1];
+    const double u3 = u[2];
+    const double normU = sqrt(u1 * u1 + u2 * u2 + u3 * u3);
+
+    const double v1 = v[0];
+    const double v2 = v[1];
+    const double v3 = v[2];
+    const double normV = sqrt(v1 * v1 + v2 * v2 + v3 * v3);
+
+    if (IsCloseToZero(normU * normV))
+    {
+      return false;
+    }
+    else
+    {
+      const double cosAngle = (u1 * v1 + u2 * v2 + u3 * v3) / (normU * normV);
+
+      return (IsCloseToZero(cosAngle - 1.0) ||      // Close to +1: Parallel, non-opposite
+              IsCloseToZero(fabs(cosAngle) - 1.0)); // Close to -1: Parallel, opposite
+    }
+  }
+
+  
   struct SliceOrdering::Instance : public boost::noncopyable
   {
   private:
     std::string   instanceId_;
     bool          hasPosition_;
     Vector        position_;   
+    bool          hasNormal_;
+    Vector        normal_;   
     bool          hasIndexInSeries_;
     size_t        indexInSeries_;
     unsigned int  framesCount_;
@@ -141,6 +198,8 @@
         position_[2] = tmp[2];
       }
 
+      hasNormal_ = ComputeNormal(normal_, instance);
+
       std::string s;
       hasIndexInSeries_ = false;
 
@@ -190,6 +249,17 @@
     {
       return framesCount_;
     }
+
+    bool HasNormal() const
+    {
+      return hasNormal_;
+    }
+
+    const Vector& GetNormal() const
+    {
+      assert(hasNormal_);
+      return normal_;
+    }
   };
 
 
@@ -226,15 +296,7 @@
       throw OrthancException(ErrorCode_UnknownResource);
     }
 
-    std::vector<float> cosines;
-    hasNormal_ = TokenizeVector(cosines, series, DICOM_TAG_IMAGE_ORIENTATION_PATIENT, 6);
-
-    if (hasNormal_)
-    {
-      normal_[0] = cosines[1] * cosines[5] - cosines[2] * cosines[4];
-      normal_[1] = cosines[2] * cosines[3] - cosines[0] * cosines[5];
-      normal_[2] = cosines[0] * cosines[4] - cosines[1] * cosines[3];
-    }
+    hasNormal_ = ComputeNormal(normal_, series);
   }
 
 
@@ -268,7 +330,10 @@
     for (size_t i = 0; i < instances_.size(); i++)
     {
       assert(instances_[i] != NULL);
-      if (!instances_[i]->HasPosition())
+
+      if (!instances_[i]->HasPosition() ||
+          (instances_[i]->HasNormal() &&
+           !IsParallelOrOpposite(instances_[i]->GetNormal(), normal_)))
       {
         return false;
       }
--- a/OrthancServer/SliceOrdering.h	Tue Aug 28 15:14:33 2018 +0200
+++ b/OrthancServer/SliceOrdering.h	Wed Aug 29 13:24:28 2018 +0200
@@ -52,6 +52,12 @@
     std::vector<Instance*>   instances_;
     bool                     isVolume_;
 
+    static bool ComputeNormal(Vector& normal,
+                              const DicomMap& dicom);
+
+    static bool IsParallelOrOpposite(const Vector& a,
+                                     const Vector& b);
+
     static bool IndexInSeriesComparator(const SliceOrdering::Instance* a,
                                         const SliceOrdering::Instance* b);
 
--- a/UnitTestsSources/DicomMapTests.cpp	Tue Aug 28 15:14:33 2018 +0200
+++ b/UnitTestsSources/DicomMapTests.cpp	Wed Aug 29 13:24:28 2018 +0200
@@ -197,6 +197,7 @@
          *it == DicomTag(0x0020, 0x0100) ||  /* TemporalPositionIdentifier, from MR Image module */
          *it == DicomTag(0x0028, 0x0008) ||  /* NumberOfFrames, from Multi-frame module attributes, related to Image */
          *it == DicomTag(0x0020, 0x0032) ||  /* ImagePositionPatient, from Image Plan module, related to Image */
+         *it == DicomTag(0x0020, 0x0037) ||  /* ImageOrientationPatient, from Image Plane Module (Orthanc 1.4.2) */
          *it == DicomTag(0x0020, 0x4000)))   /* ImageComments, from General Image module */
     {
       ok = true;