changeset 532:b22312081388 dicom-rt

extract roi geometry
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 30 Aug 2013 16:09:19 +0200
parents bd2087bb6450
children da8e064d0d49
files Core/Toolbox.cpp Core/Toolbox.h OrthancServer/FromDcmtkBridge.cpp OrthancServer/FromDcmtkBridge.h OrthancServer/RadiotherapyRestApi.cpp UnitTests/main.cpp
diffstat 6 files changed, 263 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/Core/Toolbox.cpp	Thu Aug 29 17:59:09 2013 +0200
+++ b/Core/Toolbox.cpp	Fri Aug 30 16:09:19 2013 +0200
@@ -717,4 +717,47 @@
         throw OrthancException(ErrorCode_NotImplemented);
     }
   }
+
+
+
+  void Toolbox::Split(std::vector<std::string>& result,
+                      const std::string& source,
+                      char delimiter)
+  {
+    if (source.size() == 0)
+    {
+      result.clear();
+      return;
+    }
+
+    size_t count = 1;
+    for (size_t i = 0; i < source.size(); i++)
+    {
+      if (source[i] == delimiter)
+      {
+        count++;
+      }
+    }
+
+    result.clear();
+    result.resize(count);
+
+    size_t pos = 0;
+    size_t start = 0;
+    while (start < source.size())
+    {
+      assert(pos < count);
+
+      size_t end = start;
+      while (end < source.size() && 
+             source[end] != delimiter)
+      {
+        end++;
+      }
+
+      result[pos++] = source.substr(start, end - start);
+      start = end + 1;
+    }
+  }
+
 }
--- a/Core/Toolbox.h	Thu Aug 29 17:59:09 2013 +0200
+++ b/Core/Toolbox.h	Fri Aug 30 16:09:19 2013 +0200
@@ -108,5 +108,9 @@
     void UrlDecode(std::string& s);
 
     Endianness DetectEndianness();
+
+    void Split(std::vector<std::string>& result,
+               const std::string& source,
+               char delimiter);
   }
 }
--- a/OrthancServer/FromDcmtkBridge.cpp	Thu Aug 29 17:59:09 2013 +0200
+++ b/OrthancServer/FromDcmtkBridge.cpp	Fri Aug 30 16:09:19 2013 +0200
@@ -706,13 +706,13 @@
 
 
 
-  bool ParsedDicomFile::GetTagValue(std::string& value,
-                                    const DicomTag& tag)
+  static bool GetTagValueInternal(std::string& value,
+                                  DcmItem& item,
+                                  const DicomTag& tag)
   {
     DcmTagKey k(tag.GetGroup(), tag.GetElement());
-    DcmDataset& dataset = *file_->getDataset();
     DcmElement* element = NULL;
-    if (!dataset.findAndGetElement(k, element).good() ||
+    if (!item.findAndGetElement(k, element).good() ||
         element == NULL)
     {
       return false;
@@ -729,7 +729,53 @@
       value = v->AsString();
     }
 
-    return true;
+    return true;    
+  }
+
+
+  bool ParsedDicomFile::GetTagValue(std::string& value,
+                                    const DicomTag& tag)
+  {
+    DcmDataset& dataset = *file_->getDataset();
+    return GetTagValueInternal(value, dataset, tag);
+  }
+
+
+
+  bool ParsedDicomFile::GetTagValue(std::string& value,
+                                    const SequencePath& path,
+                                    const DicomTag& tag)
+  {
+    if (path.size() == 0)
+    {
+      return GetTagValue(value, tag);
+    }
+
+    DcmItem* current = file_->getDataset();
+      assert(current != NULL);
+
+    for (SequencePath::const_iterator it = path.begin(); it != path.end(); it++)
+    {
+      DcmTagKey k(it->first.GetGroup(), it->first.GetElement());
+
+      DcmSequenceOfItems* sequence = NULL;
+      if (!current->findAndGetSequence(k, sequence).good() ||
+          sequence == NULL ||
+          sequence->getVR() != EVR_SQ)
+      {
+        return false;
+      }
+
+      if (it->second > sequence->card())
+      {
+        return false;
+      }
+
+      current = sequence->getItem(it->second);
+      assert(current != NULL);
+    }
+    
+    return GetTagValueInternal(value, *current, tag);    
   }
 
 
--- a/OrthancServer/FromDcmtkBridge.h	Thu Aug 29 17:59:09 2013 +0200
+++ b/OrthancServer/FromDcmtkBridge.h	Fri Aug 30 16:09:19 2013 +0200
@@ -72,6 +72,8 @@
                size_t size);
 
   public:
+    typedef std::list< std::pair<DicomTag, unsigned int> >  SequencePath;
+
     ParsedDicomFile(const char* content,
                     size_t size)
     {
@@ -115,6 +117,10 @@
     bool GetTagValue(std::string& value,
                      const DicomTag& tag);
 
+    bool GetTagValue(std::string& value,
+                     const SequencePath& path,
+                     const DicomTag& tag);
+
     DicomInstanceHasher GetHasher();
   };
 
--- a/OrthancServer/RadiotherapyRestApi.cpp	Thu Aug 29 17:59:09 2013 +0200
+++ b/OrthancServer/RadiotherapyRestApi.cpp	Fri Aug 30 16:09:19 2013 +0200
@@ -40,6 +40,8 @@
   ServerContext& context = contextApi.GetContext()
 
 
+// DICOM tags for RT-STRUCT
+
 #define REFERENCED_STUDY_SEQUENCE "0008,1110"
 #define REFERENCED_SOP_INSTANCE_UID "0008,1155"
 #define FRAME_OF_REFERENCE_UID "0020,0052"
@@ -51,6 +53,12 @@
 #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"
+
 
 namespace Orthanc
 {
@@ -208,13 +216,15 @@
         bool found = false;
         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) &&
-              content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i].isMember(ROI_NAME) &&
-              content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i][ROI_NUMBER]["Value"].asString() == call.GetUriComponent("roi", ""))
+          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"] = content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i][ROI_NAME]["Value"].asString();
-            result["GenerationAlgorithm"] = content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i][ROI_GENERATION_ALGORITHM]["Value"].asString();
+            result["Name"] = roi[ROI_NAME]["Value"].asString();
+            result["GenerationAlgorithm"] = roi[ROI_GENERATION_ALGORITHM]["Value"].asString();
             found = true;
           }
         }
@@ -226,13 +236,112 @@
 
         found = false;
 
+        boost::mutex::scoped_lock lock(context.GetDicomFileMutex());
+        ParsedDicomFile& dicom = context.GetDicomFile(series["Instances"][0].asString());
+
         for (Json::Value::ArrayIndex i = 0; i < content[ROI_CONTOUR_SEQUENCE]["Value"].size(); i++)
         {
-          if (content[ROI_CONTOUR_SEQUENCE]["Value"][i].isMember(REFERENCED_ROI_NUMBER) &&
-              content[ROI_CONTOUR_SEQUENCE]["Value"][i].isMember(ROI_DISPLAY_COLOR) &&
-              content[ROI_CONTOUR_SEQUENCE]["Value"][i][REFERENCED_ROI_NUMBER]["Value"].asString() == call.GetUriComponent("roi", ""))
+          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", ""))
           {
-            result["DisplayColor"] = content[ROI_CONTOUR_SEQUENCE]["Value"][i][ROI_DISPLAY_COLOR]["Value"].asString();
+            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;
+                }
+
+                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 (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;
+                }
+
+                (*target) [instance.front()].append(item);
+              }                  
+            }
+
             found = true;
           }
         }
--- a/UnitTests/main.cpp	Thu Aug 29 17:59:09 2013 +0200
+++ b/UnitTests/main.cpp	Fri Aug 30 16:09:19 2013 +0200
@@ -480,6 +480,47 @@
 }
 
 
+TEST(Toolbox, Split)
+{
+  std::vector<std::string> s;
+  
+  Toolbox::Split(s, "", '|'); 
+  ASSERT_EQ(0, s.size());
+  
+  Toolbox::Split(s, "aaaaa", '|'); 
+  ASSERT_EQ(1, s.size());
+  ASSERT_EQ("aaaaa", s[0]);
+  
+  Toolbox::Split(s, "aaa|aa", '|'); 
+  ASSERT_EQ(2, s.size());
+  ASSERT_EQ("aaa", s[0]);
+  ASSERT_EQ("aa", s[1]);
+  
+  Toolbox::Split(s, "a|aa|ab", '|'); 
+  ASSERT_EQ(3, s.size());
+  ASSERT_EQ("a", s[0]);
+  ASSERT_EQ("aa", s[1]);
+  ASSERT_EQ("ab", s[2]);
+  
+  Toolbox::Split(s, "||ab", '|'); 
+  ASSERT_EQ(3, s.size());
+  ASSERT_EQ("", s[0]);
+  ASSERT_EQ("", s[1]);
+  ASSERT_EQ("ab", s[2]);
+  
+  Toolbox::Split(s, "|", '|'); 
+  ASSERT_EQ(2, s.size());
+  ASSERT_EQ("", s[0]);
+  ASSERT_EQ("", s[1]);
+  
+  Toolbox::Split(s, "||", '|'); 
+  ASSERT_EQ(3, s.size());
+  ASSERT_EQ("", s[0]);
+  ASSERT_EQ("", s[1]);
+  ASSERT_EQ("", s[2]);
+}
+
+
 int main(int argc, char **argv)
 {
   // Initialize Google's logging library.