changeset 761:12a3f2eaa99a dicom-rt

move
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 16 Apr 2014 16:05:50 +0200
parents 8cfc6119a5bd
children 2f7802e95dd0
files CMakeLists.txt OrthancServer/OrthancRestApi/RadiotherapyRestApi.cpp OrthancServer/RadiotherapyRestApi.cpp OrthancServer/RadiotherapyRestApi.h
diffstat 4 files changed, 816 insertions(+), 860 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Wed Apr 16 16:04:55 2014 +0200
+++ b/CMakeLists.txt	Wed Apr 16 16:05:50 2014 +0200
@@ -223,7 +223,7 @@
   OrthancServer/OrthancFindRequestHandler.cpp
   OrthancServer/OrthancMoveRequestHandler.cpp
 
-  #OrthancServer/RadiotherapyRestApi.cpp
+  OrthancServer/OrthancRestApi/RadiotherapyRestApi.cpp
   )
 
 # Ensure autogenerated code is built before building ServerLibrary
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/OrthancRestApi/RadiotherapyRestApi.cpp	Wed Apr 16 16:05:50 2014 +0200
@@ -0,0 +1,815 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "RadiotherapyRestApi.h"
+
+#include "ServerToolbox.h"
+
+#define RETRIEVE_CONTEXT(call)                          \
+  OrthancRestApi& contextApi =                          \
+    dynamic_cast<OrthancRestApi&>(call.GetContext());   \
+  ServerContext& context = contextApi.GetContext()
+
+
+// DICOM tags for RT-STRUCT
+
+/**
+ * REFERENCE: http://www.dabsoft.ch/dicom/3/C.8.8.6/
+ *
+ * IMPORTANT: The points/vertices coordinates are reported in [mm]. 
+ * 
+ * TODO: Support "Contour Offset Vector"
+ **/
+
+
+#define REFERENCED_STUDY_SEQUENCE "0008,1110"
+#define REFERENCED_SOP_INSTANCE_UID "0008,1155"
+#define FRAME_OF_REFERENCE_UID "0020,0052"
+#define REFERENCED_FRAME_OF_REFERENCE_SEQUENCE "3006,0010"
+#define STRUCTURE_SET_ROI_SEQUENCE "3006,0020"
+#define ROI_NUMBER "3006,0022"
+#define ROI_NAME "3006,0026"
+#define ROI_GENERATION_ALGORITHM "3006,0036"
+#define ROI_CONTOUR_SEQUENCE "3006,0039"
+#define REFERENCED_ROI_NUMBER "3006,0084"
+#define ROI_DISPLAY_COLOR "3006,002a"
+#define CONTOUR_SEQUENCE "3006,0040"
+#define CONTOUR_IMAGE_SEQUENCE "3006,0016"
+#define CONTOUR_GEOMETRIC_TYPE "3006,0042"
+#define NUMBER_OF_CONTOUR_POINTS "3006,0046"
+#define CONTOUR_DATA "3006,0050"
+#define CONTOUR_SLAB_THICKNESS "3006,0044"
+#define SLICE_THICKNESS "0018,0050"
+
+
+
+#include <boost/geometry.hpp>
+#include <boost/geometry/geometries/point.hpp>
+#include <boost/geometry/geometries/point_xy.hpp>
+#include <boost/geometry/geometries/polygon.hpp>
+#include <boost/geometry/geometries/linestring.hpp>
+
+namespace Orthanc
+{
+  static bool CheckSeriesModality(Json::Value& study,
+                                  Json::Value& series,
+                                  Json::Value& content,
+                                  ServerContext& context,
+                                  const std::string& seriesId,
+                                  const std::string& modality)
+  {
+    if (!context.GetIndex().LookupResource(series, seriesId, ResourceType_Series))
+    {
+      return false;
+    }
+
+    // Retrieve the parent study
+    std::string studyId = series["ParentStudy"].asString();
+    if (!context.GetIndex().LookupResource(study, studyId, ResourceType_Study))
+    {
+      return false;
+    }
+
+    // Check the modality and that there is a single instance inside the series
+    if (!series["MainDicomTags"].isMember("Modality") ||
+        series["MainDicomTags"]["Modality"].asString() != modality ||
+        series["Instances"].size() != 1)
+    {
+      return false;
+    }
+
+    // Retrieve the instance data
+    std::string instanceId = series["Instances"][0].asString();
+
+    context.ReadJson(content, instanceId);
+
+    return true;
+  }
+
+
+  static bool ContourToPoints(Json::Value& result,
+                              const Json::Value& source)
+  {
+    std::vector<std::string> points;
+    Toolbox::Split(points, source.asString(), '\\');
+
+    if (points.size() % 3 != 0)
+    {
+      return false;
+    }
+
+    result = Json::arrayValue;
+
+    for (size_t k = 0; k < points.size(); k += 3)
+    {
+      Json::Value p = Json::arrayValue;
+
+      try
+      {
+        p.append(boost::lexical_cast<float>(points[k]));
+        p.append(boost::lexical_cast<float>(points[k + 1]));
+        p.append(boost::lexical_cast<float>(points[k + 2]));
+      }
+      catch (boost::bad_lexical_cast)
+      {
+        return false;
+      }
+
+      result.append(p);
+    }
+
+    return true;
+  }
+
+
+  static bool GetRtStructuresInfo(Json::Value& study,
+                                  Json::Value& series,
+                                  Json::Value& content,
+                                  std::string& frameOfReference,
+                                  ServerContext& context,
+                                  const std::string& seriesId)
+  {
+    if (!CheckSeriesModality(study, series, content, context, seriesId, "RTSTRUCT"))
+    {
+      return false;
+    }
+
+    // Check that the "ReferencedStudySequence" (if any) is the same as the parent study.
+    if (content.isMember(REFERENCED_STUDY_SEQUENCE))
+    {
+      if (content[REFERENCED_STUDY_SEQUENCE]["Value"].size() != 1 ||
+          !content[REFERENCED_STUDY_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID) ||
+          content[REFERENCED_STUDY_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString() != 
+          study["MainDicomTags"]["StudyInstanceUID"].asString())
+      {
+        return false;
+      }
+    }
+
+    // Lookup for the frame of reference. Orthanc does not support
+    // RTSTRUCT with multiple frames of reference.
+    if (!content.isMember(REFERENCED_FRAME_OF_REFERENCE_SEQUENCE) ||
+        content[REFERENCED_FRAME_OF_REFERENCE_SEQUENCE]["Value"].size() != 1 ||
+        !content[REFERENCED_FRAME_OF_REFERENCE_SEQUENCE]["Value"][0].isMember(FRAME_OF_REFERENCE_UID))
+    {
+      return false;
+    }
+
+    frameOfReference = content[REFERENCED_FRAME_OF_REFERENCE_SEQUENCE]["Value"][0][FRAME_OF_REFERENCE_UID]["Value"].asString();
+
+    return true;
+  }
+
+
+  static bool GetRtStructuresRoi(Json::Value& result,
+                                 Json::Value& contourSequence,
+                                 std::string& instanceId,
+                                 ServerContext& context,
+                                 const std::string& seriesId,
+                                 const std::string& roiNumber)
+  {
+    Json::Value study, series, content;
+    std::string frameOfReference;
+
+    if (!GetRtStructuresInfo(study, series, content, frameOfReference, context, seriesId))
+    {
+      return false;
+    }
+
+    if (!content.isMember(STRUCTURE_SET_ROI_SEQUENCE) ||
+        !content.isMember(ROI_CONTOUR_SEQUENCE))
+    {
+      return false;
+    }
+
+    instanceId = series["Instances"][0].asString();
+
+    bool found = false;
+
+    for (Json::Value::ArrayIndex i = 0; i < content[STRUCTURE_SET_ROI_SEQUENCE]["Value"].size(); i++)
+    {
+      const Json::Value& roi = content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i];
+
+      if (roi.isMember(ROI_NUMBER) &&
+          roi.isMember(ROI_NAME) &&
+          roi[ROI_NUMBER]["Value"].asString() == roiNumber)
+      {
+        result["InternalIndex"] = i;
+        result["Number"] = boost::lexical_cast<unsigned int>(roiNumber);
+        result["Name"] = roi[ROI_NAME]["Value"].asString();
+        result["GenerationAlgorithm"] = roi[ROI_GENERATION_ALGORITHM]["Value"].asString();
+        found = true;
+      }
+    }
+
+    if (!found)
+    {
+      return false;
+    }
+
+    for (Json::Value::ArrayIndex i = 0; i < content[ROI_CONTOUR_SEQUENCE]["Value"].size(); i++)
+    {
+      const Json::Value& contour = content[ROI_CONTOUR_SEQUENCE]["Value"][i];
+
+      if (contour.isMember(REFERENCED_ROI_NUMBER) &&
+          contour.isMember(ROI_DISPLAY_COLOR) &&
+          contour.isMember(CONTOUR_SEQUENCE) &&
+          contour[REFERENCED_ROI_NUMBER]["Value"].asString() == roiNumber)
+      {
+        std::vector<std::string> color;
+        Toolbox::Split(color, contour[ROI_DISPLAY_COLOR]["Value"].asString(), '\\');
+
+        result["DisplayColor"] = Json::arrayValue;
+        if (color.size() != 3)
+        {
+          return false;
+        }
+
+        for (size_t k = 0; k < color.size(); k++)
+        {
+          try
+          {
+            result["DisplayColor"].append(boost::lexical_cast<int>(color[k]));
+          }
+          catch (boost::bad_lexical_cast)
+          {
+            return false;
+          }
+        }
+
+        contourSequence = contour[CONTOUR_SEQUENCE]["Value"];
+
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+
+  static bool GetClosedPlanarPoints(Json::Value& result,
+                                    ServerContext& context,
+                                    const std::string& instanceId,
+                                    const Json::Value& roi,
+                                    unsigned int index)
+  {
+    boost::mutex::scoped_lock lock(context.GetDicomFileMutex());
+    ParsedDicomFile& dicom = context.GetDicomFile(instanceId);
+
+    ParsedDicomFile::SequencePath path;
+    path.push_back(std::make_pair(DicomTag(0x3006, 0x0039 /* ROIContourSequence */), roi["InternalIndex"].asInt()));
+    path.push_back(std::make_pair(DicomTag(0x3006, 0x0040 /* ContourSequence */), index));
+        
+    std::string contourData;
+    std::string numberOfPoints;
+
+    if (dicom.GetTagValue(contourData, path, DicomTag(0x3006, 0x0050 /* ContourData */)) &&
+        dicom.GetTagValue(numberOfPoints, path, DicomTag(0x3006, 0x0046 /* NumberOfContourPoints */)) &&
+        ContourToPoints(result, contourData) &&
+        result.size() == boost::lexical_cast<unsigned int>(numberOfPoints))
+    {
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+
+  static bool LookupReferencedInstance(Json::Value& result,
+                                       ServerContext& context,
+                                       const Json::Value& contour)
+  {
+    if (contour.isMember(CONTOUR_IMAGE_SEQUENCE) &&
+        contour[CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 &&
+        contour[CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID))
+    {
+      std::string uid = contour[CONTOUR_IMAGE_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString();
+
+      std::list<std::string> instance;
+      context.GetIndex().LookupTagValue(instance, DICOM_TAG_SOP_INSTANCE_UID, uid);
+
+      if (instance.size() == 1 &&
+          context.GetIndex().LookupResource(result, instance.front(), ResourceType_Instance))
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+
+  static bool GetRtStructuresClosedPlanarThickness(float& result,
+                                                   ServerContext& context,
+                                                   const Json::Value& contour)
+  {
+    if (contour.isMember(CONTOUR_SLAB_THICKNESS))
+    {
+      result = boost::lexical_cast<float>(contour[CONTOUR_SLAB_THICKNESS]["Value"].asString());
+      return true;
+    }
+
+    // No slab thickness is explicitely specified: Fallback to
+    // the thickness of the referred instance
+
+    Json::Value instance;
+    if (LookupReferencedInstance(instance, context, contour))
+    {
+      Json::Value info;
+      context.ReadJson(info, instance["ID"].asString());       
+
+      if (info.isMember(SLICE_THICKNESS))
+      {
+        result = boost::lexical_cast<float>(info[SLICE_THICKNESS]["Value"].asString());
+        return true;
+      }
+    }
+
+    return false;
+  }
+                                       
+
+
+  static void GetRtStructuresInfo(RestApi::GetCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
+
+    Json::Value study, series, content;
+    std::string frameOfReference;
+    if (GetRtStructuresInfo(study, series, content, frameOfReference, context, call.GetUriComponent("id", "")))
+    {
+      Json::Value result;
+
+      result["Study"] = study["ID"];
+
+
+      // Lookup the series with the same frame of reference inside this study
+      result["RelatedSeries"] = Json::arrayValue;
+
+      for (Json::Value::ArrayIndex i = 0; i < study["Series"].size(); i++)
+      {
+        Json::Value otherSeries;
+        if (context.GetIndex().LookupResource(otherSeries, study["Series"][i].asString(), ResourceType_Series) &&
+            otherSeries["Instances"].size() > 0)
+        {
+          Json::Value info;
+          context.ReadJson(info, otherSeries["Instances"][0].asString());
+
+          if (info.isMember(FRAME_OF_REFERENCE_UID))
+          {
+            result["RelatedSeries"].append(study["Series"][i].asString());
+          }
+        }
+      }
+
+
+      call.GetOutput().AnswerJson(result);
+    }
+  }
+
+
+  static void GetRtStructuresListOfROIs(RestApi::GetCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
+
+    Json::Value study, series, content;
+    std::string frameOfReference;
+    if (GetRtStructuresInfo(study, series, content, frameOfReference, context, call.GetUriComponent("id", "")))
+    {
+      Json::Value result(Json::arrayValue);
+
+      if (content.isMember(STRUCTURE_SET_ROI_SEQUENCE))
+      {
+        for (Json::Value::ArrayIndex i = 0; i < content[STRUCTURE_SET_ROI_SEQUENCE]["Value"].size(); i++)
+        {
+          if (content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i].isMember(ROI_NUMBER))
+          {
+            result.append(boost::lexical_cast<int>(content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i][ROI_NUMBER]["Value"].asString()));
+          }
+        }
+      }
+
+      call.GetOutput().AnswerJson(result);
+    }
+  }
+
+
+  static void GetRtStructuresROI(RestApi::GetCall& call)
+  {
+     RETRIEVE_CONTEXT(call);
+
+     Json::Value roi, contour;
+     std::string instanceId;
+
+     if (GetRtStructuresRoi(roi, contour, instanceId, context, 
+                            call.GetUriComponent("id", ""),
+                            call.GetUriComponent("roi", "")))
+     {
+       roi.removeMember("InternalIndex");
+       call.GetOutput().AnswerJson(roi);
+     }
+  }
+
+
+  static void GetRtStructuresROIPoints(RestApi::GetCall& call)
+  {
+     RETRIEVE_CONTEXT(call);
+
+     Json::Value roi, contour;
+     std::string instanceId;
+
+     if (GetRtStructuresRoi(roi, contour, instanceId, context, 
+                            call.GetUriComponent("id", ""),
+                            call.GetUriComponent("roi", "")))
+     {
+       Json::Value result = Json::arrayValue;
+
+       for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++)
+       {
+         if (contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "POINT")
+         {
+           Json::Value p;
+           if (ContourToPoints(p, contour[i][CONTOUR_DATA]["Value"].asString()) &&
+               p.size() == 1)
+           {
+             result.append(p[0]);
+           }
+         }
+       }
+
+       call.GetOutput().AnswerJson(result);
+     }
+  }
+
+
+  static void GetRtStructuresListOfClosedPlanars(RestApi::GetCall& call)
+  {
+     RETRIEVE_CONTEXT(call);
+
+     Json::Value roi, contour;
+     std::string instanceId;
+
+     if (GetRtStructuresRoi(roi, contour, instanceId, context, 
+                            call.GetUriComponent("id", ""),
+                            call.GetUriComponent("roi", "")))
+     {
+       Json::Value result = Json::arrayValue;
+
+       for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++)
+       {
+         if (contour[i].isMember(CONTOUR_IMAGE_SEQUENCE) &&
+             contour[i].isMember(CONTOUR_GEOMETRIC_TYPE) &&
+             contour[i].isMember(NUMBER_OF_CONTOUR_POINTS) &&
+             contour[i].isMember(CONTOUR_DATA) &&
+             contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 &&
+             contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID) &&
+             contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "CLOSED_PLANAR")
+         {
+           result.append(i);
+         }
+       }
+
+       call.GetOutput().AnswerJson(result);
+     }
+  }
+
+
+  static void GetRtStructuresSingleClosedPlanar(RestApi::GetCall& call)
+  {
+     RETRIEVE_CONTEXT(call);
+
+     Json::Value roi, contour, result;
+     std::string instanceId;
+
+     if (GetRtStructuresRoi(roi, contour, instanceId, context, 
+                            call.GetUriComponent("id", ""),
+                            call.GetUriComponent("roi", "")))
+     {
+       unsigned int index = boost::lexical_cast<unsigned int>(call.GetUriComponent("polygon", ""));
+
+       if (GetClosedPlanarPoints(result, context, instanceId, roi, index))
+       {
+         call.GetOutput().AnswerJson(result);
+       }
+     }
+  }
+
+
+  static void GetRtStructuresClosedPlanarThickness(RestApi::GetCall& call)
+  {
+     RETRIEVE_CONTEXT(call);
+
+     Json::Value roi, contour;
+     std::string instanceId;
+
+     if (GetRtStructuresRoi(roi, contour, instanceId, context, 
+                            call.GetUriComponent("id", ""),
+                            call.GetUriComponent("roi", "")))
+     {
+       unsigned int index = boost::lexical_cast<unsigned int>(call.GetUriComponent("polygon", ""));
+
+       float thickness;
+       if (GetRtStructuresClosedPlanarThickness(thickness, context, contour[index]))
+       {
+         call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(thickness), "text/plain");
+       }
+     }
+  }
+
+
+  static bool ComputeClosedPlanarArea(double& area,
+                                      const Json::Value& vertices)
+  {
+    if (vertices.size() <= 1)
+    {
+      area = 0;
+      return true;
+    }
+         
+    // Check that all the points share the same z coordinates
+    for (Json::Value::ArrayIndex i = 1; i < vertices.size(); i++)
+    {
+      static float THRESHOLD = 10.0f * std::numeric_limits<float>::epsilon();
+
+      assert(vertices[i].size() == 3);
+      if (fabs(vertices[i][2].asFloat() - vertices[0][2].asFloat()) > THRESHOLD)
+      {
+        // This point has not the same z coordinate
+        return false;
+      }
+    }
+
+    // Calculate the area of a cartesian polygon
+    // TODO - What happens if self-crossing polygon?
+    typedef boost::geometry::model::d2::point_xy<float> point_type;
+
+    boost::geometry::model::linestring<point_type> points;
+
+    points.resize(vertices.size());
+    for (Json::Value::ArrayIndex i = 0; i < vertices.size(); i++)
+    {
+      float x = vertices[i][0].asFloat();
+      float y = vertices[i][1].asFloat();
+      points[i] = boost::geometry::make<point_type>(x, y);
+    }
+
+    boost::geometry::model::polygon<point_type> poly;
+    boost::geometry::append(poly, points);
+               
+    area = abs(boost::geometry::area(poly));
+    return true;
+  }
+
+
+  static void GetRtStructuresClosedPlanarArea(RestApi::GetCall& call)
+  {
+     RETRIEVE_CONTEXT(call);
+
+     Json::Value roi, contour, vertices;
+     std::string instanceId;
+
+     if (GetRtStructuresRoi(roi, contour, instanceId, context, 
+                            call.GetUriComponent("id", ""),
+                            call.GetUriComponent("roi", "")))
+     {
+       unsigned int index = boost::lexical_cast<unsigned int>(call.GetUriComponent("polygon", ""));
+
+       double area;
+       if (GetClosedPlanarPoints(vertices, context, instanceId, roi, index) &&
+           ComputeClosedPlanarArea(area, vertices))
+       {
+         call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(area), "text/plain");
+       }
+     }
+  }
+
+
+  static void GetRtStructuresInstanceOfClosedPlanar(RestApi::GetCall& call)
+  {
+    RETRIEVE_CONTEXT(call);
+
+    Json::Value roi, contour;
+    std::string instanceId;
+
+    if (GetRtStructuresRoi(roi, contour, instanceId, context, 
+                           call.GetUriComponent("id", ""),
+                           call.GetUriComponent("roi", "")))
+    {
+      unsigned int index = boost::lexical_cast<unsigned int>(call.GetUriComponent("polygon", ""));
+
+      Json::Value result;
+      if (LookupReferencedInstance(result, context, contour[index]))
+      {
+        call.GetOutput().AnswerJson(result);
+      }
+    }
+  }
+
+
+  static void GetRtStructuresListOfInstances(RestApi::GetCall& call)
+  {
+     RETRIEVE_CONTEXT(call);
+
+     Json::Value roi, contour;
+     std::string instanceId;
+
+     if (GetRtStructuresRoi(roi, contour, instanceId, context, 
+                            call.GetUriComponent("id", ""),
+                            call.GetUriComponent("roi", "")))
+     {
+       Json::Value result = Json::arrayValue;
+
+       for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++)
+       {
+         if (contour[i].isMember(CONTOUR_IMAGE_SEQUENCE) &&
+             contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 &&
+             contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID))
+         {
+           std::string uid = contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString();
+
+           std::list<std::string> instance;
+           context.GetIndex().LookupTagValue(instance, DICOM_TAG_SOP_INSTANCE_UID, uid);
+
+           if (instance.size() == 1)
+           {
+             result.append(instance.front());
+           }
+         }
+       }
+
+       call.GetOutput().AnswerJson(result);
+     }
+  }
+
+
+
+  static void GetRtStructuresClosedPlanarsOfInstance(RestApi::GetCall& call)
+  {
+     RETRIEVE_CONTEXT(call);
+
+     Json::Value roi, contour, instance;
+     std::string instanceId;
+
+     if (context.GetIndex().LookupResource(instance, call.GetUriComponent("instance", ""), ResourceType_Instance) &&
+         GetRtStructuresRoi(roi, contour, instanceId, context, 
+                            call.GetUriComponent("id", ""),
+                            call.GetUriComponent("roi", "")))
+     {
+       Json::Value result = Json::arrayValue;
+
+       for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++)
+       {
+         if (contour[i].isMember(CONTOUR_DATA) &&
+             contour[i].isMember(CONTOUR_IMAGE_SEQUENCE) &&
+             contour[i].isMember(NUMBER_OF_CONTOUR_POINTS) &&
+             contour[i].isMember(CONTOUR_GEOMETRIC_TYPE) &&
+             contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 &&
+             contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID) &&
+             contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "CLOSED_PLANAR")
+         {
+           std::string uid = contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString();
+
+           Json::Value points;
+           if (uid == instance["MainDicomTags"]["SOPInstanceUID"].asString() &&
+               GetClosedPlanarPoints(points, context, instanceId, roi, i))
+           {
+             result.append(points);
+           }             
+         }
+       }
+
+       call.GetOutput().AnswerJson(result);
+     }
+  }
+
+
+
+  static void GetRtStructuresPointsOfInstance(RestApi::GetCall& call)
+  {
+     RETRIEVE_CONTEXT(call);
+
+     Json::Value roi, contour, instance;
+     std::string instanceId;
+
+     if (context.GetIndex().LookupResource(instance, call.GetUriComponent("instance", ""), ResourceType_Instance) &&
+         GetRtStructuresRoi(roi, contour, instanceId, context, 
+                            call.GetUriComponent("id", ""),
+                            call.GetUriComponent("roi", "")))
+     {
+       Json::Value result = Json::arrayValue;
+
+       for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++)
+       {
+         if (contour[i].isMember(CONTOUR_DATA) &&
+             contour[i].isMember(CONTOUR_IMAGE_SEQUENCE) &&
+             contour[i].isMember(CONTOUR_GEOMETRIC_TYPE) &&
+             contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 &&
+             contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID) &&
+             contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "POINT")
+         {
+           std::string uid = contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString();
+
+           if (uid == instance["MainDicomTags"]["SOPInstanceUID"].asString())
+           {
+             Json::Value p;
+             if (ContourToPoints(p, contour[i][CONTOUR_DATA]["Value"].asString()) &&
+                 p.size() == 1)
+             {
+               result.append(p[0]);
+             }
+           }
+         }
+       }
+
+       call.GetOutput().AnswerJson(result);
+     }
+  }
+
+
+  static void GetRtStructuresVolume(RestApi::GetCall& call)
+  {
+     RETRIEVE_CONTEXT(call);
+
+     Json::Value roi, contour, vertices;
+     std::string instanceId;
+
+     if (GetRtStructuresRoi(roi, contour, instanceId, context, 
+                            call.GetUriComponent("id", ""),
+                            call.GetUriComponent("roi", "")))
+     {
+       double volume = 0;
+
+       for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++)
+       {
+         double area;
+         float thickness;
+
+         if (contour[i].isMember(CONTOUR_GEOMETRIC_TYPE) &&
+             contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "CLOSED_PLANAR" &&
+             GetClosedPlanarPoints(vertices, context, instanceId, roi, i) &&
+             ComputeClosedPlanarArea(area, vertices) &&
+             GetRtStructuresClosedPlanarThickness(thickness, context, contour[i]))
+         {
+           volume += area * static_cast<double>(thickness);
+         }
+       }
+
+       call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(volume), "text/plain");
+     }
+  }
+
+
+  RadiotherapyRestApi::RadiotherapyRestApi(ServerContext& context) : OrthancRestApi(context)
+  {
+    Register("/series/{id}/rt-structures", GetRtStructuresInfo);
+    Register("/series/{id}/rt-structures/roi", GetRtStructuresListOfROIs);
+    Register("/series/{id}/rt-structures/roi/{roi}/info", GetRtStructuresROI);
+    Register("/series/{id}/rt-structures/roi/{roi}/points", GetRtStructuresROIPoints);
+    Register("/series/{id}/rt-structures/roi/{roi}/closed-planar", GetRtStructuresListOfClosedPlanars);
+    Register("/series/{id}/rt-structures/roi/{roi}/closed-planar/{polygon}/vertices", GetRtStructuresSingleClosedPlanar);
+    Register("/series/{id}/rt-structures/roi/{roi}/closed-planar/{polygon}/thickness", GetRtStructuresClosedPlanarThickness);
+    Register("/series/{id}/rt-structures/roi/{roi}/closed-planar/{polygon}/instance", GetRtStructuresInstanceOfClosedPlanar);
+    Register("/series/{id}/rt-structures/roi/{roi}/closed-planar/{polygon}/area", GetRtStructuresClosedPlanarArea);
+    Register("/series/{id}/rt-structures/roi/{roi}/instances", GetRtStructuresListOfInstances);
+    Register("/series/{id}/rt-structures/roi/{roi}/instances/{instance}/closed-planar", GetRtStructuresClosedPlanarsOfInstance);
+    Register("/series/{id}/rt-structures/roi/{roi}/instances/{instance}/points", GetRtStructuresPointsOfInstance);
+    Register("/series/{id}/rt-structures/roi/{roi}/volume", GetRtStructuresVolume);
+  }
+
+}
+
+
+//  curl http://localhost:8042/series/0b9e2bb2-605a59aa-f27c0260-9cc4faf6-9d8bf457/rt-structures
+//  curl http://localhost:8042/series/ef041e6b-c855e775-f7e0f7fe-dc3c17dc-533cb8c5/rt-structures
--- a/OrthancServer/RadiotherapyRestApi.cpp	Wed Apr 16 16:04:55 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,815 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
- * Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "RadiotherapyRestApi.h"
-
-#include "ServerToolbox.h"
-
-#define RETRIEVE_CONTEXT(call)                          \
-  OrthancRestApi& contextApi =                          \
-    dynamic_cast<OrthancRestApi&>(call.GetContext());   \
-  ServerContext& context = contextApi.GetContext()
-
-
-// DICOM tags for RT-STRUCT
-
-/**
- * REFERENCE: http://www.dabsoft.ch/dicom/3/C.8.8.6/
- *
- * IMPORTANT: The points/vertices coordinates are reported in [mm]. 
- * 
- * TODO: Support "Contour Offset Vector"
- **/
-
-
-#define REFERENCED_STUDY_SEQUENCE "0008,1110"
-#define REFERENCED_SOP_INSTANCE_UID "0008,1155"
-#define FRAME_OF_REFERENCE_UID "0020,0052"
-#define REFERENCED_FRAME_OF_REFERENCE_SEQUENCE "3006,0010"
-#define STRUCTURE_SET_ROI_SEQUENCE "3006,0020"
-#define ROI_NUMBER "3006,0022"
-#define ROI_NAME "3006,0026"
-#define ROI_GENERATION_ALGORITHM "3006,0036"
-#define ROI_CONTOUR_SEQUENCE "3006,0039"
-#define REFERENCED_ROI_NUMBER "3006,0084"
-#define ROI_DISPLAY_COLOR "3006,002a"
-#define CONTOUR_SEQUENCE "3006,0040"
-#define CONTOUR_IMAGE_SEQUENCE "3006,0016"
-#define CONTOUR_GEOMETRIC_TYPE "3006,0042"
-#define NUMBER_OF_CONTOUR_POINTS "3006,0046"
-#define CONTOUR_DATA "3006,0050"
-#define CONTOUR_SLAB_THICKNESS "3006,0044"
-#define SLICE_THICKNESS "0018,0050"
-
-
-
-#include <boost/geometry.hpp>
-#include <boost/geometry/geometries/point.hpp>
-#include <boost/geometry/geometries/point_xy.hpp>
-#include <boost/geometry/geometries/polygon.hpp>
-#include <boost/geometry/geometries/linestring.hpp>
-
-namespace Orthanc
-{
-  static bool CheckSeriesModality(Json::Value& study,
-                                  Json::Value& series,
-                                  Json::Value& content,
-                                  ServerContext& context,
-                                  const std::string& seriesId,
-                                  const std::string& modality)
-  {
-    if (!context.GetIndex().LookupResource(series, seriesId, ResourceType_Series))
-    {
-      return false;
-    }
-
-    // Retrieve the parent study
-    std::string studyId = series["ParentStudy"].asString();
-    if (!context.GetIndex().LookupResource(study, studyId, ResourceType_Study))
-    {
-      return false;
-    }
-
-    // Check the modality and that there is a single instance inside the series
-    if (!series["MainDicomTags"].isMember("Modality") ||
-        series["MainDicomTags"]["Modality"].asString() != modality ||
-        series["Instances"].size() != 1)
-    {
-      return false;
-    }
-
-    // Retrieve the instance data
-    std::string instanceId = series["Instances"][0].asString();
-
-    context.ReadJson(content, instanceId);
-
-    return true;
-  }
-
-
-  static bool ContourToPoints(Json::Value& result,
-                              const Json::Value& source)
-  {
-    std::vector<std::string> points;
-    Toolbox::Split(points, source.asString(), '\\');
-
-    if (points.size() % 3 != 0)
-    {
-      return false;
-    }
-
-    result = Json::arrayValue;
-
-    for (size_t k = 0; k < points.size(); k += 3)
-    {
-      Json::Value p = Json::arrayValue;
-
-      try
-      {
-        p.append(boost::lexical_cast<float>(points[k]));
-        p.append(boost::lexical_cast<float>(points[k + 1]));
-        p.append(boost::lexical_cast<float>(points[k + 2]));
-      }
-      catch (boost::bad_lexical_cast)
-      {
-        return false;
-      }
-
-      result.append(p);
-    }
-
-    return true;
-  }
-
-
-  static bool GetRtStructuresInfo(Json::Value& study,
-                                  Json::Value& series,
-                                  Json::Value& content,
-                                  std::string& frameOfReference,
-                                  ServerContext& context,
-                                  const std::string& seriesId)
-  {
-    if (!CheckSeriesModality(study, series, content, context, seriesId, "RTSTRUCT"))
-    {
-      return false;
-    }
-
-    // Check that the "ReferencedStudySequence" (if any) is the same as the parent study.
-    if (content.isMember(REFERENCED_STUDY_SEQUENCE))
-    {
-      if (content[REFERENCED_STUDY_SEQUENCE]["Value"].size() != 1 ||
-          !content[REFERENCED_STUDY_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID) ||
-          content[REFERENCED_STUDY_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString() != 
-          study["MainDicomTags"]["StudyInstanceUID"].asString())
-      {
-        return false;
-      }
-    }
-
-    // Lookup for the frame of reference. Orthanc does not support
-    // RTSTRUCT with multiple frames of reference.
-    if (!content.isMember(REFERENCED_FRAME_OF_REFERENCE_SEQUENCE) ||
-        content[REFERENCED_FRAME_OF_REFERENCE_SEQUENCE]["Value"].size() != 1 ||
-        !content[REFERENCED_FRAME_OF_REFERENCE_SEQUENCE]["Value"][0].isMember(FRAME_OF_REFERENCE_UID))
-    {
-      return false;
-    }
-
-    frameOfReference = content[REFERENCED_FRAME_OF_REFERENCE_SEQUENCE]["Value"][0][FRAME_OF_REFERENCE_UID]["Value"].asString();
-
-    return true;
-  }
-
-
-  static bool GetRtStructuresRoi(Json::Value& result,
-                                 Json::Value& contourSequence,
-                                 std::string& instanceId,
-                                 ServerContext& context,
-                                 const std::string& seriesId,
-                                 const std::string& roiNumber)
-  {
-    Json::Value study, series, content;
-    std::string frameOfReference;
-
-    if (!GetRtStructuresInfo(study, series, content, frameOfReference, context, seriesId))
-    {
-      return false;
-    }
-
-    if (!content.isMember(STRUCTURE_SET_ROI_SEQUENCE) ||
-        !content.isMember(ROI_CONTOUR_SEQUENCE))
-    {
-      return false;
-    }
-
-    instanceId = series["Instances"][0].asString();
-
-    bool found = false;
-
-    for (Json::Value::ArrayIndex i = 0; i < content[STRUCTURE_SET_ROI_SEQUENCE]["Value"].size(); i++)
-    {
-      const Json::Value& roi = content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i];
-
-      if (roi.isMember(ROI_NUMBER) &&
-          roi.isMember(ROI_NAME) &&
-          roi[ROI_NUMBER]["Value"].asString() == roiNumber)
-      {
-        result["InternalIndex"] = i;
-        result["Number"] = boost::lexical_cast<unsigned int>(roiNumber);
-        result["Name"] = roi[ROI_NAME]["Value"].asString();
-        result["GenerationAlgorithm"] = roi[ROI_GENERATION_ALGORITHM]["Value"].asString();
-        found = true;
-      }
-    }
-
-    if (!found)
-    {
-      return false;
-    }
-
-    for (Json::Value::ArrayIndex i = 0; i < content[ROI_CONTOUR_SEQUENCE]["Value"].size(); i++)
-    {
-      const Json::Value& contour = content[ROI_CONTOUR_SEQUENCE]["Value"][i];
-
-      if (contour.isMember(REFERENCED_ROI_NUMBER) &&
-          contour.isMember(ROI_DISPLAY_COLOR) &&
-          contour.isMember(CONTOUR_SEQUENCE) &&
-          contour[REFERENCED_ROI_NUMBER]["Value"].asString() == roiNumber)
-      {
-        std::vector<std::string> color;
-        Toolbox::Split(color, contour[ROI_DISPLAY_COLOR]["Value"].asString(), '\\');
-
-        result["DisplayColor"] = Json::arrayValue;
-        if (color.size() != 3)
-        {
-          return false;
-        }
-
-        for (size_t k = 0; k < color.size(); k++)
-        {
-          try
-          {
-            result["DisplayColor"].append(boost::lexical_cast<int>(color[k]));
-          }
-          catch (boost::bad_lexical_cast)
-          {
-            return false;
-          }
-        }
-
-        contourSequence = contour[CONTOUR_SEQUENCE]["Value"];
-
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-
-  static bool GetClosedPlanarPoints(Json::Value& result,
-                                    ServerContext& context,
-                                    const std::string& instanceId,
-                                    const Json::Value& roi,
-                                    unsigned int index)
-  {
-    boost::mutex::scoped_lock lock(context.GetDicomFileMutex());
-    ParsedDicomFile& dicom = context.GetDicomFile(instanceId);
-
-    ParsedDicomFile::SequencePath path;
-    path.push_back(std::make_pair(DicomTag(0x3006, 0x0039 /* ROIContourSequence */), roi["InternalIndex"].asInt()));
-    path.push_back(std::make_pair(DicomTag(0x3006, 0x0040 /* ContourSequence */), index));
-        
-    std::string contourData;
-    std::string numberOfPoints;
-
-    if (dicom.GetTagValue(contourData, path, DicomTag(0x3006, 0x0050 /* ContourData */)) &&
-        dicom.GetTagValue(numberOfPoints, path, DicomTag(0x3006, 0x0046 /* NumberOfContourPoints */)) &&
-        ContourToPoints(result, contourData) &&
-        result.size() == boost::lexical_cast<unsigned int>(numberOfPoints))
-    {
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-
-  static bool LookupReferencedInstance(Json::Value& result,
-                                       ServerContext& context,
-                                       const Json::Value& contour)
-  {
-    if (contour.isMember(CONTOUR_IMAGE_SEQUENCE) &&
-        contour[CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 &&
-        contour[CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID))
-    {
-      std::string uid = contour[CONTOUR_IMAGE_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString();
-
-      std::list<std::string> instance;
-      context.GetIndex().LookupTagValue(instance, DICOM_TAG_SOP_INSTANCE_UID, uid);
-
-      if (instance.size() == 1 &&
-          context.GetIndex().LookupResource(result, instance.front(), ResourceType_Instance))
-      {
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-
-  static bool GetRtStructuresClosedPlanarThickness(float& result,
-                                                   ServerContext& context,
-                                                   const Json::Value& contour)
-  {
-    if (contour.isMember(CONTOUR_SLAB_THICKNESS))
-    {
-      result = boost::lexical_cast<float>(contour[CONTOUR_SLAB_THICKNESS]["Value"].asString());
-      return true;
-    }
-
-    // No slab thickness is explicitely specified: Fallback to
-    // the thickness of the referred instance
-
-    Json::Value instance;
-    if (LookupReferencedInstance(instance, context, contour))
-    {
-      Json::Value info;
-      context.ReadJson(info, instance["ID"].asString());       
-
-      if (info.isMember(SLICE_THICKNESS))
-      {
-        result = boost::lexical_cast<float>(info[SLICE_THICKNESS]["Value"].asString());
-        return true;
-      }
-    }
-
-    return false;
-  }
-                                       
-
-
-  static void GetRtStructuresInfo(RestApi::GetCall& call)
-  {
-    RETRIEVE_CONTEXT(call);
-
-    Json::Value study, series, content;
-    std::string frameOfReference;
-    if (GetRtStructuresInfo(study, series, content, frameOfReference, context, call.GetUriComponent("id", "")))
-    {
-      Json::Value result;
-
-      result["Study"] = study["ID"];
-
-
-      // Lookup the series with the same frame of reference inside this study
-      result["RelatedSeries"] = Json::arrayValue;
-
-      for (Json::Value::ArrayIndex i = 0; i < study["Series"].size(); i++)
-      {
-        Json::Value otherSeries;
-        if (context.GetIndex().LookupResource(otherSeries, study["Series"][i].asString(), ResourceType_Series) &&
-            otherSeries["Instances"].size() > 0)
-        {
-          Json::Value info;
-          context.ReadJson(info, otherSeries["Instances"][0].asString());
-
-          if (info.isMember(FRAME_OF_REFERENCE_UID))
-          {
-            result["RelatedSeries"].append(study["Series"][i].asString());
-          }
-        }
-      }
-
-
-      call.GetOutput().AnswerJson(result);
-    }
-  }
-
-
-  static void GetRtStructuresListOfROIs(RestApi::GetCall& call)
-  {
-    RETRIEVE_CONTEXT(call);
-
-    Json::Value study, series, content;
-    std::string frameOfReference;
-    if (GetRtStructuresInfo(study, series, content, frameOfReference, context, call.GetUriComponent("id", "")))
-    {
-      Json::Value result(Json::arrayValue);
-
-      if (content.isMember(STRUCTURE_SET_ROI_SEQUENCE))
-      {
-        for (Json::Value::ArrayIndex i = 0; i < content[STRUCTURE_SET_ROI_SEQUENCE]["Value"].size(); i++)
-        {
-          if (content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i].isMember(ROI_NUMBER))
-          {
-            result.append(boost::lexical_cast<int>(content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i][ROI_NUMBER]["Value"].asString()));
-          }
-        }
-      }
-
-      call.GetOutput().AnswerJson(result);
-    }
-  }
-
-
-  static void GetRtStructuresROI(RestApi::GetCall& call)
-  {
-     RETRIEVE_CONTEXT(call);
-
-     Json::Value roi, contour;
-     std::string instanceId;
-
-     if (GetRtStructuresRoi(roi, contour, instanceId, context, 
-                            call.GetUriComponent("id", ""),
-                            call.GetUriComponent("roi", "")))
-     {
-       roi.removeMember("InternalIndex");
-       call.GetOutput().AnswerJson(roi);
-     }
-  }
-
-
-  static void GetRtStructuresROIPoints(RestApi::GetCall& call)
-  {
-     RETRIEVE_CONTEXT(call);
-
-     Json::Value roi, contour;
-     std::string instanceId;
-
-     if (GetRtStructuresRoi(roi, contour, instanceId, context, 
-                            call.GetUriComponent("id", ""),
-                            call.GetUriComponent("roi", "")))
-     {
-       Json::Value result = Json::arrayValue;
-
-       for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++)
-       {
-         if (contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "POINT")
-         {
-           Json::Value p;
-           if (ContourToPoints(p, contour[i][CONTOUR_DATA]["Value"].asString()) &&
-               p.size() == 1)
-           {
-             result.append(p[0]);
-           }
-         }
-       }
-
-       call.GetOutput().AnswerJson(result);
-     }
-  }
-
-
-  static void GetRtStructuresListOfClosedPlanars(RestApi::GetCall& call)
-  {
-     RETRIEVE_CONTEXT(call);
-
-     Json::Value roi, contour;
-     std::string instanceId;
-
-     if (GetRtStructuresRoi(roi, contour, instanceId, context, 
-                            call.GetUriComponent("id", ""),
-                            call.GetUriComponent("roi", "")))
-     {
-       Json::Value result = Json::arrayValue;
-
-       for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++)
-       {
-         if (contour[i].isMember(CONTOUR_IMAGE_SEQUENCE) &&
-             contour[i].isMember(CONTOUR_GEOMETRIC_TYPE) &&
-             contour[i].isMember(NUMBER_OF_CONTOUR_POINTS) &&
-             contour[i].isMember(CONTOUR_DATA) &&
-             contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 &&
-             contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID) &&
-             contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "CLOSED_PLANAR")
-         {
-           result.append(i);
-         }
-       }
-
-       call.GetOutput().AnswerJson(result);
-     }
-  }
-
-
-  static void GetRtStructuresSingleClosedPlanar(RestApi::GetCall& call)
-  {
-     RETRIEVE_CONTEXT(call);
-
-     Json::Value roi, contour, result;
-     std::string instanceId;
-
-     if (GetRtStructuresRoi(roi, contour, instanceId, context, 
-                            call.GetUriComponent("id", ""),
-                            call.GetUriComponent("roi", "")))
-     {
-       unsigned int index = boost::lexical_cast<unsigned int>(call.GetUriComponent("polygon", ""));
-
-       if (GetClosedPlanarPoints(result, context, instanceId, roi, index))
-       {
-         call.GetOutput().AnswerJson(result);
-       }
-     }
-  }
-
-
-  static void GetRtStructuresClosedPlanarThickness(RestApi::GetCall& call)
-  {
-     RETRIEVE_CONTEXT(call);
-
-     Json::Value roi, contour;
-     std::string instanceId;
-
-     if (GetRtStructuresRoi(roi, contour, instanceId, context, 
-                            call.GetUriComponent("id", ""),
-                            call.GetUriComponent("roi", "")))
-     {
-       unsigned int index = boost::lexical_cast<unsigned int>(call.GetUriComponent("polygon", ""));
-
-       float thickness;
-       if (GetRtStructuresClosedPlanarThickness(thickness, context, contour[index]))
-       {
-         call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(thickness), "text/plain");
-       }
-     }
-  }
-
-
-  static bool ComputeClosedPlanarArea(double& area,
-                                      const Json::Value& vertices)
-  {
-    if (vertices.size() <= 1)
-    {
-      area = 0;
-      return true;
-    }
-         
-    // Check that all the points share the same z coordinates
-    for (Json::Value::ArrayIndex i = 1; i < vertices.size(); i++)
-    {
-      static float THRESHOLD = 10.0f * std::numeric_limits<float>::epsilon();
-
-      assert(vertices[i].size() == 3);
-      if (fabs(vertices[i][2].asFloat() - vertices[0][2].asFloat()) > THRESHOLD)
-      {
-        // This point has not the same z coordinate
-        return false;
-      }
-    }
-
-    // Calculate the area of a cartesian polygon
-    // TODO - What happens if self-crossing polygon?
-    typedef boost::geometry::model::d2::point_xy<float> point_type;
-
-    boost::geometry::model::linestring<point_type> points;
-
-    points.resize(vertices.size());
-    for (Json::Value::ArrayIndex i = 0; i < vertices.size(); i++)
-    {
-      float x = vertices[i][0].asFloat();
-      float y = vertices[i][1].asFloat();
-      points[i] = boost::geometry::make<point_type>(x, y);
-    }
-
-    boost::geometry::model::polygon<point_type> poly;
-    boost::geometry::append(poly, points);
-               
-    area = abs(boost::geometry::area(poly));
-    return true;
-  }
-
-
-  static void GetRtStructuresClosedPlanarArea(RestApi::GetCall& call)
-  {
-     RETRIEVE_CONTEXT(call);
-
-     Json::Value roi, contour, vertices;
-     std::string instanceId;
-
-     if (GetRtStructuresRoi(roi, contour, instanceId, context, 
-                            call.GetUriComponent("id", ""),
-                            call.GetUriComponent("roi", "")))
-     {
-       unsigned int index = boost::lexical_cast<unsigned int>(call.GetUriComponent("polygon", ""));
-
-       double area;
-       if (GetClosedPlanarPoints(vertices, context, instanceId, roi, index) &&
-           ComputeClosedPlanarArea(area, vertices))
-       {
-         call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(area), "text/plain");
-       }
-     }
-  }
-
-
-  static void GetRtStructuresInstanceOfClosedPlanar(RestApi::GetCall& call)
-  {
-    RETRIEVE_CONTEXT(call);
-
-    Json::Value roi, contour;
-    std::string instanceId;
-
-    if (GetRtStructuresRoi(roi, contour, instanceId, context, 
-                           call.GetUriComponent("id", ""),
-                           call.GetUriComponent("roi", "")))
-    {
-      unsigned int index = boost::lexical_cast<unsigned int>(call.GetUriComponent("polygon", ""));
-
-      Json::Value result;
-      if (LookupReferencedInstance(result, context, contour[index]))
-      {
-        call.GetOutput().AnswerJson(result);
-      }
-    }
-  }
-
-
-  static void GetRtStructuresListOfInstances(RestApi::GetCall& call)
-  {
-     RETRIEVE_CONTEXT(call);
-
-     Json::Value roi, contour;
-     std::string instanceId;
-
-     if (GetRtStructuresRoi(roi, contour, instanceId, context, 
-                            call.GetUriComponent("id", ""),
-                            call.GetUriComponent("roi", "")))
-     {
-       Json::Value result = Json::arrayValue;
-
-       for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++)
-       {
-         if (contour[i].isMember(CONTOUR_IMAGE_SEQUENCE) &&
-             contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 &&
-             contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID))
-         {
-           std::string uid = contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString();
-
-           std::list<std::string> instance;
-           context.GetIndex().LookupTagValue(instance, DICOM_TAG_SOP_INSTANCE_UID, uid);
-
-           if (instance.size() == 1)
-           {
-             result.append(instance.front());
-           }
-         }
-       }
-
-       call.GetOutput().AnswerJson(result);
-     }
-  }
-
-
-
-  static void GetRtStructuresClosedPlanarsOfInstance(RestApi::GetCall& call)
-  {
-     RETRIEVE_CONTEXT(call);
-
-     Json::Value roi, contour, instance;
-     std::string instanceId;
-
-     if (context.GetIndex().LookupResource(instance, call.GetUriComponent("instance", ""), ResourceType_Instance) &&
-         GetRtStructuresRoi(roi, contour, instanceId, context, 
-                            call.GetUriComponent("id", ""),
-                            call.GetUriComponent("roi", "")))
-     {
-       Json::Value result = Json::arrayValue;
-
-       for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++)
-       {
-         if (contour[i].isMember(CONTOUR_DATA) &&
-             contour[i].isMember(CONTOUR_IMAGE_SEQUENCE) &&
-             contour[i].isMember(NUMBER_OF_CONTOUR_POINTS) &&
-             contour[i].isMember(CONTOUR_GEOMETRIC_TYPE) &&
-             contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 &&
-             contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID) &&
-             contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "CLOSED_PLANAR")
-         {
-           std::string uid = contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString();
-
-           Json::Value points;
-           if (uid == instance["MainDicomTags"]["SOPInstanceUID"].asString() &&
-               GetClosedPlanarPoints(points, context, instanceId, roi, i))
-           {
-             result.append(points);
-           }             
-         }
-       }
-
-       call.GetOutput().AnswerJson(result);
-     }
-  }
-
-
-
-  static void GetRtStructuresPointsOfInstance(RestApi::GetCall& call)
-  {
-     RETRIEVE_CONTEXT(call);
-
-     Json::Value roi, contour, instance;
-     std::string instanceId;
-
-     if (context.GetIndex().LookupResource(instance, call.GetUriComponent("instance", ""), ResourceType_Instance) &&
-         GetRtStructuresRoi(roi, contour, instanceId, context, 
-                            call.GetUriComponent("id", ""),
-                            call.GetUriComponent("roi", "")))
-     {
-       Json::Value result = Json::arrayValue;
-
-       for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++)
-       {
-         if (contour[i].isMember(CONTOUR_DATA) &&
-             contour[i].isMember(CONTOUR_IMAGE_SEQUENCE) &&
-             contour[i].isMember(CONTOUR_GEOMETRIC_TYPE) &&
-             contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 &&
-             contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID) &&
-             contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "POINT")
-         {
-           std::string uid = contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString();
-
-           if (uid == instance["MainDicomTags"]["SOPInstanceUID"].asString())
-           {
-             Json::Value p;
-             if (ContourToPoints(p, contour[i][CONTOUR_DATA]["Value"].asString()) &&
-                 p.size() == 1)
-             {
-               result.append(p[0]);
-             }
-           }
-         }
-       }
-
-       call.GetOutput().AnswerJson(result);
-     }
-  }
-
-
-  static void GetRtStructuresVolume(RestApi::GetCall& call)
-  {
-     RETRIEVE_CONTEXT(call);
-
-     Json::Value roi, contour, vertices;
-     std::string instanceId;
-
-     if (GetRtStructuresRoi(roi, contour, instanceId, context, 
-                            call.GetUriComponent("id", ""),
-                            call.GetUriComponent("roi", "")))
-     {
-       double volume = 0;
-
-       for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++)
-       {
-         double area;
-         float thickness;
-
-         if (contour[i].isMember(CONTOUR_GEOMETRIC_TYPE) &&
-             contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "CLOSED_PLANAR" &&
-             GetClosedPlanarPoints(vertices, context, instanceId, roi, i) &&
-             ComputeClosedPlanarArea(area, vertices) &&
-             GetRtStructuresClosedPlanarThickness(thickness, context, contour[i]))
-         {
-           volume += area * static_cast<double>(thickness);
-         }
-       }
-
-       call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(volume), "text/plain");
-     }
-  }
-
-
-  RadiotherapyRestApi::RadiotherapyRestApi(ServerContext& context) : OrthancRestApi(context)
-  {
-    Register("/series/{id}/rt-structures", GetRtStructuresInfo);
-    Register("/series/{id}/rt-structures/roi", GetRtStructuresListOfROIs);
-    Register("/series/{id}/rt-structures/roi/{roi}/info", GetRtStructuresROI);
-    Register("/series/{id}/rt-structures/roi/{roi}/points", GetRtStructuresROIPoints);
-    Register("/series/{id}/rt-structures/roi/{roi}/closed-planar", GetRtStructuresListOfClosedPlanars);
-    Register("/series/{id}/rt-structures/roi/{roi}/closed-planar/{polygon}/vertices", GetRtStructuresSingleClosedPlanar);
-    Register("/series/{id}/rt-structures/roi/{roi}/closed-planar/{polygon}/thickness", GetRtStructuresClosedPlanarThickness);
-    Register("/series/{id}/rt-structures/roi/{roi}/closed-planar/{polygon}/instance", GetRtStructuresInstanceOfClosedPlanar);
-    Register("/series/{id}/rt-structures/roi/{roi}/closed-planar/{polygon}/area", GetRtStructuresClosedPlanarArea);
-    Register("/series/{id}/rt-structures/roi/{roi}/instances", GetRtStructuresListOfInstances);
-    Register("/series/{id}/rt-structures/roi/{roi}/instances/{instance}/closed-planar", GetRtStructuresClosedPlanarsOfInstance);
-    Register("/series/{id}/rt-structures/roi/{roi}/instances/{instance}/points", GetRtStructuresPointsOfInstance);
-    Register("/series/{id}/rt-structures/roi/{roi}/volume", GetRtStructuresVolume);
-  }
-
-}
-
-
-//  curl http://localhost:8042/series/0b9e2bb2-605a59aa-f27c0260-9cc4faf6-9d8bf457/rt-structures
-//  curl http://localhost:8042/series/ef041e6b-c855e775-f7e0f7fe-dc3c17dc-533cb8c5/rt-structures
--- a/OrthancServer/RadiotherapyRestApi.h	Wed Apr 16 16:04:55 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
- * Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "OrthancRestApi.h"
-
-namespace Orthanc
-{
-  class RadiotherapyRestApi : public OrthancRestApi
-  {
-  public:
-    RadiotherapyRestApi(ServerContext& context);
-  };
-}