changeset 535:da8e064d0d49 dicom-rt

rth api refactored
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 03 Sep 2013 17:19:02 +0200
parents b22312081388
children 505d6deb9947
files OrthancServer/FromDcmtkBridge.cpp OrthancServer/RadiotherapyRestApi.cpp
diffstat 2 files changed, 295 insertions(+), 143 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancServer/FromDcmtkBridge.cpp	Fri Aug 30 16:09:19 2013 +0200
+++ b/OrthancServer/FromDcmtkBridge.cpp	Tue Sep 03 17:19:02 2013 +0200
@@ -766,13 +766,17 @@
         return false;
       }
 
-      if (it->second > sequence->card())
+      if (it->second < 0 || it->second > sequence->card())
       {
         return false;
       }
 
       current = sequence->getItem(it->second);
-      assert(current != NULL);
+
+      if (current == NULL)
+      {
+        return false;
+      }
     }
     
     return GetTagValueInternal(value, *current, tag);    
--- a/OrthancServer/RadiotherapyRestApi.cpp	Fri Aug 30 16:09:19 2013 +0200
+++ b/OrthancServer/RadiotherapyRestApi.cpp	Tue Sep 03 17:19:02 2013 +0200
@@ -58,6 +58,7 @@
 #define CONTOUR_GEOMETRIC_TYPE "3006,0042"
 #define NUMBER_OF_CONTOUR_POINTS "3006,0046"
 #define CONTOUR_DATA "3006,0050"
+#define CONTOUR_SLAB_THICKNESS "3006,0044"
 
 
 namespace Orthanc
@@ -99,6 +100,41 @@
   }
 
 
+  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,
@@ -136,6 +172,92 @@
   }
 
 
+  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 void GetRtStructuresInfo(RestApi::GetCall& call)
   {
     RETRIEVE_CONTEXT(call);
@@ -190,7 +312,7 @@
         {
           if (content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i].isMember(ROI_NUMBER))
           {
-            result.append(content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i][ROI_NUMBER]["Value"].asString());
+            result.append(boost::lexical_cast<int>(content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i][ROI_NUMBER]["Value"].asString()));
           }
         }
       }
@@ -202,151 +324,172 @@
 
   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].isMember(CONTOUR_SLAB_THICKNESS) &&
+             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;
+     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", ""));
+
+       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));
+        
+       Json::Value result;
+       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))
+       {
+         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", ""));
+
+       if (contour[index].isMember(CONTOUR_SLAB_THICKNESS))
+       {
+         std::cout <<contour[index][CONTOUR_SLAB_THICKNESS] << std::endl;
+         call.GetOutput().AnswerBuffer(contour[index][CONTOUR_SLAB_THICKNESS]["Value"].asString(), "text/plain");
+       }
+       else
+       {
+         // TODO - No slab thickness is explicitely specified: Should we
+         // fallback to the thickness of the instance?
+       }
+     }
+  }
+
+
+  static void GetRtStructuresClosedPlanarInstance(RestApi::GetCall& call)
+  {
     RETRIEVE_CONTEXT(call);
 
-    Json::Value study, series, content;
-    std::string frameOfReference;
-    if (GetRtStructuresInfo(study, series, content, frameOfReference, context, call.GetUriComponent("id", "")))
-    {
-      if (content.isMember(STRUCTURE_SET_ROI_SEQUENCE) &&
-          content.isMember(ROI_CONTOUR_SEQUENCE))
-      {
-        Json::Value result;
-
-        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() == call.GetUriComponent("roi", ""))
-          {
-            result["Number"] = call.GetUriComponent("roi", "");
-            result["Name"] = roi[ROI_NAME]["Value"].asString();
-            result["GenerationAlgorithm"] = roi[ROI_GENERATION_ALGORITHM]["Value"].asString();
-            found = true;
-          }
-        }
-
-        if (!found)
-        {
-          return;
-        }
-
-        found = false;
-
-        boost::mutex::scoped_lock lock(context.GetDicomFileMutex());
-        ParsedDicomFile& dicom = context.GetDicomFile(series["Instances"][0].asString());
+    Json::Value roi, contour;
+    std::string instanceId;
 
-        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() == call.GetUriComponent("roi", ""))
-          {
-            std::vector<std::string> color;
-            Toolbox::Split(color, contour[ROI_DISPLAY_COLOR]["Value"].asString(), '\\');
-
-            result["Points"] = Json::objectValue;
-            result["ClosedPlanar"] = Json::objectValue;
-            result["DisplayColor"] = Json::arrayValue;
-            for (size_t k = 0; k < color.size(); k++)
-            {
-              result["DisplayColor"].append(boost::lexical_cast<int>(color[k]));
-            }
-
-            for (Json::Value::ArrayIndex j = 0; j < contour[CONTOUR_SEQUENCE]["Value"].size(); j++)
-            {
-              const Json::Value& contourSequence = contour[CONTOUR_SEQUENCE]["Value"][j];
-
-              if (contourSequence.isMember(CONTOUR_IMAGE_SEQUENCE) &&
-                  contourSequence.isMember(CONTOUR_GEOMETRIC_TYPE) &&
-                  contourSequence.isMember(NUMBER_OF_CONTOUR_POINTS) &&
-                  contourSequence.isMember(CONTOUR_DATA) &&
-                  contourSequence[CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 &&
-                  contourSequence[CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID))
-              {
-                const std::string type = contourSequence[CONTOUR_GEOMETRIC_TYPE]["Value"].asString();
-                if (type != "POINT" && type != "CLOSED_PLANAR")
-                {
-                  continue;
-                }
+    if (GetRtStructuresRoi(roi, contour, instanceId, context, 
+                           call.GetUriComponent("id", ""),
+                           call.GetUriComponent("roi", "")))
+    {
+      unsigned int index = boost::lexical_cast<unsigned int>(call.GetUriComponent("polygon", ""));
 
-                const std::string uid = (contourSequence[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)
-                {
-                  continue;
-                }
-
-                unsigned int countPoints = boost::lexical_cast<unsigned int>
-                  (contourSequence[NUMBER_OF_CONTOUR_POINTS]["Value"].asString());
-                if (countPoints <= 0)
-                {
-                  continue;
-                }
-
-                ParsedDicomFile::SequencePath path;
-                path.push_back(std::make_pair(DicomTag(0x3006, 0x0039 /* ROIContourSequence */), i));
-                path.push_back(std::make_pair(DicomTag(0x3006, 0x0040 /* ContourSequence */), j));
-                
-                std::string contourData;
-                dicom.GetTagValue(contourData, path, DicomTag(0x3006, 0x0050 /* ContourData */));
-
-                std::vector<std::string> points;
-                Toolbox::Split(points, contourData, '\\');
-
-                Json::Value* target;
-                Json::Value item = Json::arrayValue;
+      if (contour[index].isMember(CONTOUR_IMAGE_SEQUENCE) &&
+          contour[index][CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 &&
+          contour[index][CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID))
+      {
+        std::string uid = contour[index][CONTOUR_IMAGE_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString();
 
-                if (type == "POINT" && 
-                    countPoints == 1 && 
-                    points.size() == 3)
-                {
-                  target = &result["Points"];
-                  item.append(boost::lexical_cast<float>(points[0]));
-                  item.append(boost::lexical_cast<float>(points[1]));
-                  item.append(boost::lexical_cast<float>(points[2]));
-                }
-                else if (type == "CLOSED_PLANAR" &&
-                         points.size() == 3 * countPoints)
-                {
-                  target = &result["ClosedPlanar"];
-                  for (size_t k = 0; k < countPoints; k++)
-                  {
-                    Json::Value p = Json::arrayValue;
-                    p.append(boost::lexical_cast<float>(points[3 * k]));
-                    p.append(boost::lexical_cast<float>(points[3 * k + 1]));
-                    p.append(boost::lexical_cast<float>(points[3 * k + 2]));
-                    item.append(p);
-                  }
-                }
-                else
-                {
-                  continue;
-                }
-              
-                if (!target->isMember(instance.front()))
-                {
-                  (*target) [instance.front()] = Json::arrayValue;
-                }
+        std::list<std::string> instance;
+        context.GetIndex().LookupTagValue(instance, DICOM_TAG_SOP_INSTANCE_UID, uid);
 
-                (*target) [instance.front()].append(item);
-              }                  
-            }
-
-            found = true;
-          }
-        }
-
-        if (found)
+        Json::Value result;
+        if (instance.size() == 1 &&
+            context.GetIndex().LookupResource(result, instance.front(), ResourceType_Instance))
         {
           call.GetOutput().AnswerJson(result);
         }
@@ -359,7 +502,12 @@
   {
     Register("/series/{id}/rt-structures", GetRtStructuresInfo);
     Register("/series/{id}/rt-structures/roi", GetRtStructuresListOfROIs);
-    Register("/series/{id}/rt-structures/roi/{roi}", GetRtStructuresROI);
+    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}/points", GetRtStructuresSingleClosedPlanar);
+    Register("/series/{id}/rt-structures/roi/{roi}/closed-planar/{polygon}/thickness", GetRtStructuresClosedPlanarThickness);
+    Register("/series/{id}/rt-structures/roi/{roi}/closed-planar/{polygon}/instance", GetRtStructuresClosedPlanarInstance);
   }
 
 }