changeset 1584:bd180f97c734

parsing osirix annotations
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 21 Oct 2020 17:33:17 +0200
parents c8644706e78b
children 94edbfa64c97
files OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake OrthancStone/Sources/Toolbox/OsiriX/AngleAnnotation.cpp OrthancStone/Sources/Toolbox/OsiriX/AngleAnnotation.h OrthancStone/Sources/Toolbox/OsiriX/Annotation.cpp OrthancStone/Sources/Toolbox/OsiriX/Annotation.h OrthancStone/Sources/Toolbox/OsiriX/ArrayValue.cpp OrthancStone/Sources/Toolbox/OsiriX/ArrayValue.h OrthancStone/Sources/Toolbox/OsiriX/CollectionOfAnnotations.cpp OrthancStone/Sources/Toolbox/OsiriX/CollectionOfAnnotations.h OrthancStone/Sources/Toolbox/OsiriX/DictionaryValue.cpp OrthancStone/Sources/Toolbox/OsiriX/DictionaryValue.h OrthancStone/Sources/Toolbox/OsiriX/IValue.cpp OrthancStone/Sources/Toolbox/OsiriX/IValue.h OrthancStone/Sources/Toolbox/OsiriX/IntegerValue.h OrthancStone/Sources/Toolbox/OsiriX/LineAnnotation.cpp OrthancStone/Sources/Toolbox/OsiriX/LineAnnotation.h OrthancStone/Sources/Toolbox/OsiriX/RealValue.h OrthancStone/Sources/Toolbox/OsiriX/StringValue.cpp OrthancStone/Sources/Toolbox/OsiriX/StringValue.h OrthancStone/Sources/Toolbox/OsiriX/TextAnnotation.cpp OrthancStone/Sources/Toolbox/OsiriX/TextAnnotation.h OrthancStone/UnitTestsSources/CMakeLists.txt
diffstat 22 files changed, 1474 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake	Tue Oct 20 20:21:21 2020 +0200
+++ b/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake	Wed Oct 21 17:33:17 2020 +0200
@@ -270,6 +270,21 @@
 endif()
 
 
+if (ENABLE_PUGIXML)
+  list(APPEND ORTHANC_STONE_SOURCES
+    ${ORTHANC_STONE_ROOT}/Sources/Toolbox/OsiriX/AngleAnnotation.cpp
+    ${ORTHANC_STONE_ROOT}/Sources/Toolbox/OsiriX/Annotation.cpp
+    ${ORTHANC_STONE_ROOT}/Sources/Toolbox/OsiriX/ArrayValue.cpp
+    ${ORTHANC_STONE_ROOT}/Sources/Toolbox/OsiriX/CollectionOfAnnotations.cpp
+    ${ORTHANC_STONE_ROOT}/Sources/Toolbox/OsiriX/DictionaryValue.cpp
+    ${ORTHANC_STONE_ROOT}/Sources/Toolbox/OsiriX/IValue.cpp
+    ${ORTHANC_STONE_ROOT}/Sources/Toolbox/OsiriX/LineAnnotation.cpp
+    ${ORTHANC_STONE_ROOT}/Sources/Toolbox/OsiriX/StringValue.cpp
+    ${ORTHANC_STONE_ROOT}/Sources/Toolbox/OsiriX/TextAnnotation.cpp
+    )
+endif()
+
+
 list(APPEND ORTHANC_STONE_SOURCES
   ${ORTHANC_STONE_ROOT}/Sources/Toolbox/OrthancDatasets/DicomDatasetReader.cpp
   ${ORTHANC_STONE_ROOT}/Sources/Toolbox/OrthancDatasets/DicomPath.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Toolbox/OsiriX/AngleAnnotation.cpp	Wed Oct 21 17:33:17 2020 +0200
@@ -0,0 +1,60 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "AngleAnnotation.h"
+
+#include "ArrayValue.h"
+#include "IntegerValue.h"
+#include "StringValue.h"
+
+#include <OrthancException.h>
+
+
+namespace OrthancStone
+{
+  namespace OsiriX
+  {
+    AngleAnnotation::AngleAnnotation(const DictionaryValue& dict)
+    {
+      SetupCommon(dict);
+    
+      const IntegerValue& number = dynamic_cast<const IntegerValue&>(dict.GetValue("NumberOfPoints"));
+      const ArrayValue& points = dynamic_cast<const ArrayValue&>(dict.GetValue("Point_mm"));
+
+      if (number.GetValue() != 3 ||
+          points.GetSize() != 3)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      dynamic_cast<const StringValue&>(points.GetValue(0)).ParseVector(a_);
+      dynamic_cast<const StringValue&>(points.GetValue(1)).ParseVector(center_);
+      dynamic_cast<const StringValue&>(points.GetValue(2)).ParseVector(b_);
+
+      if (a_.size() != 3u ||
+          center_.size() != 3u ||
+          b_.size() != 3u)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Toolbox/OsiriX/AngleAnnotation.h	Wed Oct 21 17:33:17 2020 +0200
@@ -0,0 +1,64 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Annotation.h"
+
+#include "../LinearAlgebra.h"  // For "Vector"
+
+
+namespace OrthancStone
+{
+  namespace OsiriX
+  {
+    class AngleAnnotation : public Annotation
+    {
+    private:
+      Vector  a_;
+      Vector  center_;
+      Vector  b_;
+
+    public:
+      AngleAnnotation(const DictionaryValue& dict);
+
+      virtual Type GetType() const ORTHANC_OVERRIDE
+      {
+        return Type_Angle;
+      }
+
+      const Vector& GetA() const
+      {
+        return a_;
+      }
+
+      const Vector& GetB() const
+      {
+        return b_;
+      }
+
+      const Vector& GetCenter() const
+      {
+        return center_;
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Toolbox/OsiriX/Annotation.cpp	Wed Oct 21 17:33:17 2020 +0200
@@ -0,0 +1,104 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Annotation.h"
+
+#include "AngleAnnotation.h"
+#include "IntegerValue.h"
+#include "LineAnnotation.h"
+#include "StringValue.h"
+#include "TextAnnotation.h"
+
+#include <Logging.h>
+
+namespace OrthancStone
+{
+  namespace OsiriX
+  {
+    void Annotation::SetupCommon(const DictionaryValue& dict)
+    {
+      const IValue* value = dict.LookupValue("Name");
+      if (value == NULL)
+      {
+        name_.clear();
+      }
+      else
+      {
+        name_ = dynamic_cast<const StringValue&>(*value).GetValue();
+      }
+    
+      value = dict.LookupValue("StudyInstanceUID");
+      if (value == NULL)
+      {
+        studyInstanceUid_.clear();
+      }
+      else
+      {
+        studyInstanceUid_ = dynamic_cast<const StringValue&>(*value).GetValue();
+      }
+    
+      value = dict.LookupValue("SeriesInstanceUID");
+      if (value == NULL)
+      {
+        seriesInstanceUid_.clear();
+      }
+      else
+      {
+        seriesInstanceUid_ = dynamic_cast<const StringValue&>(*value).GetValue();
+      }
+
+      value = dict.LookupValue("SOPInstanceUID");
+      if (value == NULL)
+      {
+        sopInstanceUid_.clear();
+      }
+      else
+      {
+        sopInstanceUid_ = dynamic_cast<const StringValue&>(*value).GetValue();
+      }
+    }
+    
+
+    Annotation* Annotation::Create(const DictionaryValue& dict)
+    {
+      const IntegerValue& type = dynamic_cast<const IntegerValue&>(dict.GetValue("Type"));
+
+      switch (type.GetValue())
+      {
+        case 5:
+          return new LineAnnotation(dict, false);
+
+        case 12:
+          return new AngleAnnotation(dict);
+
+        case 13:
+          return new TextAnnotation(dict);
+
+        case 14:
+          return new LineAnnotation(dict, true);
+
+        default:
+          LOG(WARNING) << "Unsupported OsiriX annotation type: " << type.GetValue();
+          return NULL;
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Toolbox/OsiriX/Annotation.h	Wed Oct 21 17:33:17 2020 +0200
@@ -0,0 +1,79 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "DictionaryValue.h"
+
+
+namespace OrthancStone
+{
+  namespace OsiriX
+  {
+    class Annotation : public boost::noncopyable
+    {
+    private:
+      std::string   name_;
+      std::string   studyInstanceUid_;
+      std::string   seriesInstanceUid_;
+      std::string   sopInstanceUid_;
+
+    protected:
+      void SetupCommon(const DictionaryValue& dict);
+      
+    public:
+      enum Type
+      {
+        Type_Angle,
+        Type_Line,
+        Type_Text
+      };
+
+      virtual ~Annotation()
+      {
+      }
+
+      virtual Type GetType() const = 0;
+
+      const std::string& GetName() const
+      {
+        return name_;
+      }
+
+      const std::string& GetStudyInstanceUid() const
+      {
+        return studyInstanceUid_;
+      }
+
+      const std::string& GetSeriesInstanceUid() const
+      {
+        return seriesInstanceUid_;
+      }
+
+      const std::string& GetSopInstanceUid() const
+      {
+        return sopInstanceUid_;
+      }
+
+      static Annotation* Create(const DictionaryValue& dict);
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Toolbox/OsiriX/ArrayValue.cpp	Wed Oct 21 17:33:17 2020 +0200
@@ -0,0 +1,67 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ArrayValue.h"
+
+#include <OrthancException.h>
+
+
+namespace OrthancStone
+{
+  namespace OsiriX
+  {
+    ArrayValue::~ArrayValue()
+    {
+      for (size_t i = 0; i < content_.size(); i++)
+      {
+        assert(content_[i] != NULL);
+        delete content_[i];
+      }
+    }
+
+
+    void ArrayValue::Append(IValue* item)  // Takes ownership
+    {
+      if (item == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+      else
+      {
+        content_.push_back(item);
+      }
+    }
+
+
+    const IValue& ArrayValue::GetValue(size_t i) const
+    {
+      if (i >= content_.size())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+      else
+      {
+        assert(content_[i] != NULL);
+        return *content_[i];
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Toolbox/OsiriX/ArrayValue.h	Wed Oct 21 17:33:17 2020 +0200
@@ -0,0 +1,62 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IValue.h"
+
+#include <Compatibility.h>
+
+#include <vector>
+
+namespace OrthancStone
+{
+  namespace OsiriX
+  {
+    class ArrayValue : public IValue
+    {
+    private:
+      std::vector<IValue*>  content_;
+  
+    public:
+      virtual ~ArrayValue();
+
+      virtual Type GetType() const ORTHANC_OVERRIDE
+      {
+        return Type_Array;
+      }
+
+      void Append(IValue* item);  // Takes ownership
+
+      void Reserve(size_t n)
+      {
+        content_.reserve(n);
+      }
+  
+      size_t GetSize() const
+      {
+        return content_.size();
+      }
+
+      const IValue& GetValue(size_t i) const;
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Toolbox/OsiriX/CollectionOfAnnotations.cpp	Wed Oct 21 17:33:17 2020 +0200
@@ -0,0 +1,145 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "CollectionOfAnnotations.h"
+
+#include "ArrayValue.h"
+#include "IntegerValue.h"
+
+#include <OrthancException.h>
+
+#include <cassert>
+
+namespace OrthancStone
+{
+  namespace OsiriX
+  {
+    static void GetAttributes(std::map<std::string, std::string>& target,
+                              const pugi::xml_node& node)
+    {
+      for (pugi::xml_attribute attr = node.first_attribute(); attr; attr = attr.next_attribute())
+      {
+        target[attr.name()] = attr.value();
+      }
+    }
+
+      
+    CollectionOfAnnotations::~CollectionOfAnnotations()
+    {
+      for (size_t i = 0; i < annotations_.size(); i++)
+      {
+        assert(annotations_[i] != NULL);
+        delete annotations_[i];
+      }
+    }
+
+
+    const Annotation& CollectionOfAnnotations::GetAnnotation(size_t i) const
+    {
+      if (i >= annotations_.size())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+      else
+      {
+        assert(annotations_[i] != NULL);
+        return *annotations_[i];
+      }
+    }
+    
+
+    void CollectionOfAnnotations::AddAnnotation(Annotation* annotation)
+    {
+      if (annotation == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+      else
+      {
+        annotations_.push_back(annotation);
+      }
+    }
+
+    void CollectionOfAnnotations::ParseXml(const std::string& xml)
+    {
+      pugi::xml_document doc;
+      pugi::xml_parse_result result = doc.load_buffer(xml.empty() ? NULL : xml.c_str(), xml.size());
+      if (!result)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+        
+      const pugi::xml_node& root = doc.document_element();
+      if (std::string(root.name()) != "plist" ||
+          !root.first_child() ||
+          root.first_child() != root.last_child())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      std::map<std::string, std::string> attributes;
+      GetAttributes(attributes, root);
+
+      std::map<std::string, std::string>::const_iterator version = attributes.find("version");
+      if (version == attributes.end() ||
+          version->second != "1.0")
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      std::unique_ptr<IValue> value(IValue::Parse(root.first_child()));
+
+      const DictionaryValue& dict = dynamic_cast<const DictionaryValue&>(*value);
+
+      std::set<std::string> annotations;
+      dict.GetMembers(annotations);
+
+      for (std::set<std::string>::const_iterator
+             it = annotations.begin(); it != annotations.end(); ++it)
+      {
+        const ArrayValue& images = dynamic_cast<const ArrayValue&>(dict.GetValue(*it));
+
+        for (size_t i = 0; i < images.GetSize(); i++)
+        {
+          const DictionaryValue& image = dynamic_cast<const DictionaryValue&>(images.GetValue(i));
+          const IntegerValue& number = dynamic_cast<const IntegerValue&>(image.GetValue("NumberOfROIs"));
+          const ArrayValue& rois = dynamic_cast<const ArrayValue&>(image.GetValue("ROIs"));
+          
+          if (static_cast<int64_t>(rois.GetSize()) != number.GetValue())
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+          }
+
+          for (size_t j = 0; j < rois.GetSize(); j++)
+          {
+            const DictionaryValue& roi = dynamic_cast<const DictionaryValue&>(rois.GetValue(i));
+
+            std::unique_ptr<Annotation> annotation(Annotation::Create(roi));
+            if (annotation.get() != NULL)
+            {
+              AddAnnotation(annotation.release());
+            }
+          }
+        }
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Toolbox/OsiriX/CollectionOfAnnotations.h	Wed Oct 21 17:33:17 2020 +0200
@@ -0,0 +1,53 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Annotation.h"
+
+#include <vector>
+
+namespace OrthancStone
+{
+  namespace OsiriX
+  {
+    class CollectionOfAnnotations : public boost::noncopyable
+    {
+    private:
+      std::vector<Annotation*>  annotations_;
+
+    public:
+      ~CollectionOfAnnotations();
+
+      size_t GetSize() const
+      {
+        return annotations_.size();
+      }
+
+      const Annotation& GetAnnotation(size_t i) const;
+
+      void AddAnnotation(Annotation* annotation);  // takes ownership
+
+      // Parse an XML from OsiriX
+      void ParseXml(const std::string& xml);
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Toolbox/OsiriX/DictionaryValue.cpp	Wed Oct 21 17:33:17 2020 +0200
@@ -0,0 +1,108 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DictionaryValue.h"
+
+#include <OrthancException.h>
+
+
+namespace OrthancStone
+{
+  namespace OsiriX
+  {
+    DictionaryValue::~DictionaryValue()
+    {
+      for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
+      {
+        assert(it->second != NULL);
+        delete it->second;
+      }
+    }
+
+
+    void DictionaryValue::SetValue(const std::string& key,
+                                   IValue* value /* takes ownership */)
+    {
+      if (value == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+      else
+      {
+        std::unique_ptr<IValue> protection(value);
+
+        Content::iterator found = content_.find(key);
+        if (found == content_.end())
+        {
+          content_[key] = protection.release();
+        }
+        else
+        {
+          assert(found->second != NULL);
+          delete found->second;
+          found->second = protection.release();
+        }
+      }
+    }
+      
+
+    const IValue* DictionaryValue::LookupValue(const std::string& key) const
+    {
+      Content::const_iterator found = content_.find(key);
+
+      if (found == content_.end())
+      {
+        return NULL;
+      }
+      else
+      {
+        assert(found->second != NULL);
+        return found->second;
+      }
+    }
+
+
+    const IValue& DictionaryValue::GetValue(const std::string& key) const
+    {
+      const IValue* value = LookupValue(key);
+      if (value == NULL)
+      {
+        // "HasValue()" should have been called
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+      else
+      {
+        return *value;
+      }
+    }
+
+      
+    void DictionaryValue::GetMembers(std::set<std::string>& target) const
+    {
+      target.clear();
+
+      for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
+      {
+        target.insert(it->first);
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Toolbox/OsiriX/DictionaryValue.h	Wed Oct 21 17:33:17 2020 +0200
@@ -0,0 +1,68 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IValue.h"
+
+#include <Compatibility.h>
+
+#include <map>
+#include <set>
+#include <string>
+
+
+namespace OrthancStone
+{
+  namespace OsiriX
+  {
+    class DictionaryValue : public IValue
+    {
+    private:
+      typedef std::map<std::string, IValue*>  Content;
+
+      Content  content_;
+
+    public:
+      virtual ~DictionaryValue();
+
+      virtual Type GetType() const ORTHANC_OVERRIDE
+      {
+        return Type_Dictionary;
+      }
+
+      void SetValue(const std::string& key,
+                    IValue* value /* takes ownership */);
+      
+      // Will return "false" if no such item
+      const IValue* LookupValue(const std::string& key) const;
+      
+      bool HasValue(const std::string& key) const
+      {
+        return LookupValue(key) != NULL;
+      }
+
+      const IValue& GetValue(const std::string& key) const;
+      
+      void GetMembers(std::set<std::string>& target) const;
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Toolbox/OsiriX/IValue.cpp	Wed Oct 21 17:33:17 2020 +0200
@@ -0,0 +1,119 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ArrayValue.h"
+#include "DictionaryValue.h"
+#include "IntegerValue.h"
+#include "RealValue.h"
+#include "StringValue.h"
+
+#include <OrthancException.h>
+#include <Toolbox.h>
+
+
+namespace OrthancStone
+{
+  namespace OsiriX
+  {
+    IValue* IValue::Parse(const pugi::xml_node& node)
+    {
+      const std::string name(node.name());
+  
+      if (name == "dict")
+      {
+        std::unique_ptr<DictionaryValue> dict(new DictionaryValue);
+
+        for (pugi::xml_node child = node.first_child(); child; child = child.next_sibling())
+        {
+          const std::string name2(child.name());
+
+          if (name2 != "key" ||
+              child.text().get() == NULL)
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+          }
+
+          const std::string key(child.text().get());
+
+          child = child.next_sibling();
+          if (!child)
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+          }
+
+          dict->SetValue(key, IValue::Parse(child));
+        }
+
+        return dict.release();
+      }
+      else if (name == "array")
+      {
+        std::unique_ptr<ArrayValue> array(new ArrayValue);
+
+        for (pugi::xml_node child = node.first_child(); child; child = child.next_sibling())
+        {
+          array->Append(IValue::Parse(child));
+        }
+    
+        return array.release();
+      }
+      else if (name == "integer")
+      {
+        const std::string s = Orthanc::Toolbox::StripSpaces(node.text().get());
+      
+        try
+        {
+          int64_t value = boost::lexical_cast<int64_t>(s);
+          return new IntegerValue(value);
+        }
+        catch (boost::bad_lexical_cast&)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                          "Cannot parse an integer: " + s);
+        }
+      }    
+      else if (name == "real")
+      {
+        const std::string s = Orthanc::Toolbox::StripSpaces(node.text().get());
+
+        try
+        {
+          double value = boost::lexical_cast<double>(s);
+          return new RealValue(value);
+        }
+        catch (boost::bad_lexical_cast&)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                          "Cannot parse a real number: " + s);
+        }
+      }    
+      else if (name == "string")
+      {
+        return new StringValue(node.text().get());
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Unknown XML element: " + name);
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Toolbox/OsiriX/IValue.h	Wed Oct 21 17:33:17 2020 +0200
@@ -0,0 +1,66 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+#pragma once
+
+#if !defined(ORTHANC_ENABLE_PUGIXML)
+#  error The macro ORTHANC_ENABLE_PUGIXML must be defined
+#endif
+
+#if ORTHANC_ENABLE_PUGIXML != 1
+#  error Support for XML (pugixml) must be enabled to use this file
+#endif
+
+#include <pugixml.hpp>
+#include <boost/noncopyable.hpp>
+
+namespace OrthancStone
+{
+  namespace OsiriX
+  {
+    /**
+     * This is actually a compatibility layer above the "property
+     * lists" from Apple, that are used by OsiriX to store the ROIs as
+     * XML files.  https://www.apple.com/DTDs/PropertyList-1.0.dtd
+     **/
+    class IValue : public boost::noncopyable
+    {
+    public:
+      enum Type
+      {
+        Type_Array,
+        Type_Dictionary,
+        Type_Real,
+        Type_Integer,
+        Type_String
+      };
+
+      virtual ~IValue()
+      {
+      }
+
+      virtual Type GetType() const = 0;
+
+      static IValue* Parse(const pugi::xml_node& node);
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Toolbox/OsiriX/IntegerValue.h	Wed Oct 21 17:33:17 2020 +0200
@@ -0,0 +1,57 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IValue.h"
+
+#include <Compatibility.h>
+
+#include <stdint.h>
+
+
+namespace OrthancStone
+{
+  namespace OsiriX
+  {
+    class IntegerValue : public IValue
+    {
+    private:
+      int64_t  value_;
+
+    public:
+      IntegerValue(int64_t value) :
+        value_(value)
+      {
+      }
+
+      virtual Type GetType() const ORTHANC_OVERRIDE
+      {
+        return Type_Integer;
+      }
+
+      int64_t GetValue() const
+      {
+        return value_;
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Toolbox/OsiriX/LineAnnotation.cpp	Wed Oct 21 17:33:17 2020 +0200
@@ -0,0 +1,60 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "LineAnnotation.h"
+
+#include "ArrayValue.h"
+#include "IntegerValue.h"
+#include "StringValue.h"
+
+#include <OrthancException.h>
+
+
+namespace OrthancStone
+{
+  namespace OsiriX
+  {
+    LineAnnotation::LineAnnotation(const DictionaryValue& dict,
+                                   bool isArrow) :
+      isArrow_(isArrow)
+    {
+      SetupCommon(dict);
+    
+      const IntegerValue& number = dynamic_cast<const IntegerValue&>(dict.GetValue("NumberOfPoints"));
+      const ArrayValue& points = dynamic_cast<const ArrayValue&>(dict.GetValue("Point_mm"));
+
+      if (number.GetValue() != 2 ||
+          points.GetSize() != 2)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      dynamic_cast<const StringValue&>(points.GetValue(0)).ParseVector(p1_);
+      dynamic_cast<const StringValue&>(points.GetValue(1)).ParseVector(p2_);
+
+      if (p1_.size() != 3u ||
+          p2_.size() != 3u)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Toolbox/OsiriX/LineAnnotation.h	Wed Oct 21 17:33:17 2020 +0200
@@ -0,0 +1,64 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Annotation.h"
+#include "../LinearAlgebra.h"  // For "Vector"
+
+
+namespace OrthancStone
+{
+  namespace OsiriX
+  {
+    class LineAnnotation : public Annotation
+    {
+    private:
+      Vector  p1_;
+      Vector  p2_;
+      bool    isArrow_;
+
+    public:
+      LineAnnotation(const DictionaryValue& dict,
+                     bool isArrow);
+
+      virtual Type GetType() const ORTHANC_OVERRIDE
+      {
+        return Type_Line;
+      }
+
+      const Vector& GetPoint1() const
+      {
+        return p1_;
+      }
+
+      const Vector& GetPoint2() const
+      {
+        return p2_;
+      }
+
+      bool IsArrow() const
+      {
+        return isArrow_;
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Toolbox/OsiriX/RealValue.h	Wed Oct 21 17:33:17 2020 +0200
@@ -0,0 +1,55 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IValue.h"
+
+#include <Compatibility.h>
+
+
+namespace OrthancStone
+{
+  namespace OsiriX
+  {
+    class RealValue : public IValue
+    {
+    private:
+      double  value_;
+
+    public:
+      RealValue(double value) :
+        value_(value)
+      {
+      }
+
+      virtual Type GetType() const ORTHANC_OVERRIDE
+      {
+        return Type_Real;
+      }
+
+      double GetValue() const
+      {
+        return value_;
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Toolbox/OsiriX/StringValue.cpp	Wed Oct 21 17:33:17 2020 +0200
@@ -0,0 +1,66 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "StringValue.h"
+
+#include <OrthancException.h>
+#include <Toolbox.h>
+
+#include <boost/lexical_cast.hpp>
+
+
+namespace OrthancStone
+{
+  namespace OsiriX
+  {
+    void StringValue::ParseVector(Vector& v) const
+    {
+      size_t a = value_.find('(');
+      size_t b = value_.rfind(')');
+      if (a == std::string::npos ||
+          b == std::string::npos ||
+          a >= b)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Cannot parse vector: " + value_);
+      }
+      else
+      {
+        std::vector<std::string> tokens;
+        Orthanc::Toolbox::TokenizeString(tokens, value_.substr(a + 1, b - (a + 1)), ',');
+
+        v.resize(tokens.size());
+        for (size_t i = 0; i < tokens.size(); i++)
+        {
+          try
+          {
+            v[i] = boost::lexical_cast<double>(Orthanc::Toolbox::StripSpaces(tokens[i]));
+          }
+          catch (boost::bad_lexical_cast&)
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                            "Not a real number: " + tokens[i]);
+          }
+        }
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Toolbox/OsiriX/StringValue.h	Wed Oct 21 17:33:17 2020 +0200
@@ -0,0 +1,60 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IValue.h"
+#include "../LinearAlgebra.h"  // For "Vector"
+
+#include <Compatibility.h>
+
+#include <string>
+
+
+namespace OrthancStone
+{
+  namespace OsiriX
+  {
+    class StringValue : public IValue
+    {
+    private:
+      std::string  value_;
+
+    public:
+      StringValue(const std::string& value) :
+        value_(value)
+      {
+      }
+
+      virtual Type GetType() const ORTHANC_OVERRIDE
+      {
+        return Type_Dictionary;
+      }
+
+      const std::string& GetValue() const
+      {
+        return value_;
+      }
+
+      void ParseVector(Vector& v) const;
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Toolbox/OsiriX/TextAnnotation.cpp	Wed Oct 21 17:33:17 2020 +0200
@@ -0,0 +1,44 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "TextAnnotation.h"
+
+#include "StringValue.h"
+
+#include <OrthancException.h>
+
+namespace OrthancStone
+{
+  namespace OsiriX
+  {
+    TextAnnotation::TextAnnotation(const DictionaryValue& dict)
+    {
+      SetupCommon(dict);
+    
+      dynamic_cast<const StringValue&>(dict.GetValue("Center")).ParseVector(center_);
+
+      if (center_.size() != 3u)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Toolbox/OsiriX/TextAnnotation.h	Wed Oct 21 17:33:17 2020 +0200
@@ -0,0 +1,57 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Annotation.h"
+
+#include "../LinearAlgebra.h"  // For "Vector"
+
+
+namespace OrthancStone
+{
+  namespace OsiriX
+  {
+    class TextAnnotation : public Annotation
+    {
+    private:
+      Vector  center_;
+
+    public:
+      TextAnnotation(const DictionaryValue& dict);
+
+      virtual Type GetType() const ORTHANC_OVERRIDE
+      {
+        return Type_Text;
+      }
+
+      const Vector& GetCenter() const
+      {
+        return center_;
+      }
+
+      const std::string& GetText() const
+      {
+        return GetName();   // This is just an alias
+      }
+    };
+  }
+}
--- a/OrthancStone/UnitTestsSources/CMakeLists.txt	Tue Oct 20 20:21:21 2020 +0200
+++ b/OrthancStone/UnitTestsSources/CMakeLists.txt	Wed Oct 21 17:33:17 2020 +0200
@@ -18,6 +18,7 @@
 endif()
 
 set(ENABLE_OPENGL OFF)
+set(ENABLE_PUGIXML ON)
 
 include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake)