changeset 570:90d017623910 annotations

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 01 Sep 2026 21:24:14 +0200
parents d8b367955b10
children c4f3e08e0c44
files ViewerPlugin/Annotations/AnnotationsRestApi.cpp ViewerPlugin/Annotations/AnnotationsWorkspaceId.cpp ViewerPlugin/Annotations/AnnotationsWorkspaceId.h ViewerPlugin/Annotations/ISerializable.cpp ViewerPlugin/Annotations/ISerializable.h ViewerPlugin/CMakeLists.txt ViewerPlugin/ViewerToolbox.cpp ViewerPlugin/ViewerToolbox.h
diffstat 8 files changed, 365 insertions(+), 192 deletions(-) [+]
line wrap: on
line diff
--- a/ViewerPlugin/Annotations/AnnotationsRestApi.cpp	Tue Sep 01 21:05:06 2026 +0200
+++ b/ViewerPlugin/Annotations/AnnotationsRestApi.cpp	Tue Sep 01 21:24:14 2026 +0200
@@ -27,7 +27,9 @@
 #include "../../Framework/BackgroundColor.h"
 #include "../ViewerConfiguration.h"
 #include "../ViewerToolbox.h"
+#include "AnnotationsWorkspaceId.h"
 #include "IAuthenticatedUser.h"
+#include "ISerializable.h"
 
 #include <Cache/SharedObjectCache.h>
 #include <Compression/GzipCompressor.h>
@@ -42,105 +44,6 @@
 
 namespace OrthancWSI
 {
-  class AnnotationsWorkspaceId
-  {
-  private:
-    std::string            projectId_;
-    Orthanc::ResourceType  level_;
-    std::string            resourceId_;
-    unsigned int           frameNumber_;
-
-    std::string GetKeyPrefix() const
-    {
-      switch (level_)
-      {
-      case Orthanc::ResourceType_Series:
-        return projectId_ + "|series|" + resourceId_;
-
-      case Orthanc::ResourceType_Instance:
-        return projectId_ + "|instance|" + boost::lexical_cast<std::string>(frameNumber_) + "|" + resourceId_;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-    }
-
-  public:
-    AnnotationsWorkspaceId(const std::string& projectId,
-                           Orthanc::ResourceType level,
-                           const std::string& resourceId,
-                           unsigned int frameNumber) :
-      projectId_(projectId),
-      level_(level),
-      resourceId_(resourceId),
-      frameNumber_(frameNumber)
-    {
-      if (level_ != Orthanc::ResourceType_Series &&
-          level_ != Orthanc::ResourceType_Instance)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-      }
-
-      // The pipe symbol is disallowed as it is used to build the key, cf. GetKey()
-      if (!Orthanc::Toolbox::IsAsciiString(projectId_) ||
-          projectId_.find('|') != std::string::npos)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
-                                        "Project name containing non-ASCII characters or the pipe symbol: " + projectId_);
-      }
-
-      if (!Orthanc::Toolbox::IsAsciiString(resourceId_) ||
-          resourceId_.find('|') != std::string::npos)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
-                                        "Resource ID containing non-ASCII characters or the pipe symbol: " + resourceId_);
-      }
-    }
-
-    const std::string& GetProjectId() const
-    {
-      return projectId_;
-    }
-
-    Orthanc::ResourceType GetLevel() const
-    {
-      return level_;
-    }
-
-    const std::string& GetResourceId() const
-    {
-      return resourceId_;
-    }
-
-    unsigned int GetFrameNumber() const
-    {
-      if (level_ == Orthanc::ResourceType_Instance)
-      {
-        return frameNumber_;
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    }
-
-    std::string GetInfoKey() const
-    {
-      return GetKeyPrefix() + "|info";
-    }
-
-    std::string GetSettingsKey(const UserId& user) const
-    {
-      return GetKeyPrefix() + "|settings|" + user.GetKey();
-    }
-
-    std::string GetFeaturesKey(const UserId& user) const
-    {
-      return GetKeyPrefix() + "|features|" + user.GetKey();
-    }
-  };
-
-
   static const char* const KEY_AUTHOR = "author";
   static const char* const KEY_COLOR = "color";
   static const char* const KEY_FEATURES = "features";
@@ -154,27 +57,6 @@
   static const char* const KEY_VERSION = "version";
   static const char* const KEY_VISIBLE = "visible";
 
-  static const char* const KEY_VALUE_STORE = "wsi";
-
-
-  class ISerializable : public boost::noncopyable
-  {
-  public:
-    virtual ~ISerializable()
-    {
-    }
-
-    virtual void Serialize(Json::Value& serialized) const = 0;
-
-    static void Serialize(std::string& serialized,
-                          const ISerializable& obj)
-    {
-      Json::Value value;
-      obj.Serialize(value);
-      Orthanc::Toolbox::WriteFastJson(serialized, value);
-    }
-  };
-
 
   class ILayer : public ISerializable
   {
@@ -330,71 +212,6 @@
   };
 
 
-  static void SetKeyValueStore(const std::string& key,
-                               const std::string& value)
-  {
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 8)
-    OrthancPlugins::KeyValueStore store(KEY_VALUE_STORE);
-    store.Store(key, value);
-#else
-    LOG(WARNING) << "Your Orthanc SDK is too old to save annotations";
-#endif
-  }
-
-
-  static void SetKeyValueStore(const std::string& key,
-                               const Json::Value& value)
-  {
-    std::string s;
-    Orthanc::Toolbox::WriteFastJson(s, value);
-    SetKeyValueStore(key, s);
-  }
-
-
-  static void SetKeyValueStore(const std::string& key,
-                               const ISerializable& value)
-  {
-    std::string s;
-    ISerializable::Serialize(s, value);
-    SetKeyValueStore(key, s);
-  }
-
-
-  static bool LookupKeyValueStore(std::string& value,
-                                  const std::string& key)
-  {
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 8)
-    OrthancPlugins::KeyValueStore store(KEY_VALUE_STORE);
-    return store.GetValue(value, key);
-#else
-    LOG(WARNING) << "Your Orthanc SDK is too old to load annotations";
-    return false;
-#endif
-  }
-
-
-  static bool LookupKeyValueStore(Json::Value& value,
-                                  const std::string& key)
-  {
-    std::string s;
-    if (LookupKeyValueStore(s, key))
-    {
-      if (Orthanc::Toolbox::ReadJson(value, s))
-      {
-        return true;
-      }
-      else
-      {
-        LOG(WARNING) << "Discarding incorrect JSON in the key-value store: " << key;
-        return false;
-      }
-    }
-    else
-    {
-      return false;
-    }
-  }
-
 
   static const char* const KEY_ACTIVE_USERS = "active-users";
   static const char* const KEY_PROJECT_NAME = "project-name";
@@ -873,7 +690,7 @@
       const std::string key = id_.GetSettingsKey(user);
 
       Json::Value layers;
-      if (LookupKeyValueStore(layers, key))
+      if (ViewerToolbox::LookupKeyValueStore(layers, key))
       {
         std::unique_ptr<UserAnnotationsSettings> item(new UserAnnotationsSettings(layers));
 
@@ -902,7 +719,7 @@
 
       Json::Value info;
 
-      if (LookupKeyValueStore(info, key))
+      if (ViewerToolbox::LookupKeyValueStore(info, key))
       {
         persistentInfo_.reset(new PersistentInfo(info));
 
@@ -916,7 +733,7 @@
       {
         persistentInfo_.reset(new PersistentInfo);
         persistentInfo_->Serialize(info);
-        SetKeyValueStore(key, info);
+        ViewerToolbox::SetKeyValueStore(key, info);
       }
     }
 
@@ -1124,7 +941,7 @@
 
       void Commit()
       {
-        SetKeyValueStore(that_.id_.GetSettingsKey(userId_), *userSettings_);
+        ISerializable::SetKeyValueStore(that_.id_.GetSettingsKey(userId_), *userSettings_);
       }
 
     public:
@@ -1137,7 +954,7 @@
         if (that.persistentInfo_->AddActiveUser(userId_))
         {
           // Only update the key-value store if this is the first time we meet this user
-          SetKeyValueStore(that.id_.GetInfoKey(), *that.persistentInfo_);
+          ISerializable::SetKeyValueStore(that.id_.GetInfoKey(), *that.persistentInfo_);
         }
 
         Content::iterator found = that.content_.find(userId_);
@@ -1483,7 +1300,7 @@
       content_ = Json::arrayValue;
 
       std::string compressed;
-      if (LookupKeyValueStore(compressed, key_))
+      if (ViewerToolbox::LookupKeyValueStore(compressed, key_))
       {
         std::string uncompressed;
         Orthanc::GzipCompressor compressor;
@@ -1534,7 +1351,7 @@
       Orthanc::GzipCompressor compressor;
       Orthanc::IBufferCompressor::Compress(compressed, compressor, serialized);
 
-      SetKeyValueStore(key_, compressed);
+      ViewerToolbox::SetKeyValueStore(key_, compressed);
     }
 
   public:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ViewerPlugin/Annotations/AnnotationsWorkspaceId.cpp	Tue Sep 01 21:24:14 2026 +0200
@@ -0,0 +1,112 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, 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 "../../Framework/PrecompiledHeadersWSI.h"
+#include "AnnotationsWorkspaceId.h"
+
+#include <OrthancException.h>
+#include <Toolbox.h>
+
+#include <boost/lexical_cast.hpp>
+
+
+namespace OrthancWSI
+{
+  std::string AnnotationsWorkspaceId::GetKeyPrefix() const
+  {
+    switch (level_)
+    {
+      case Orthanc::ResourceType_Series:
+        return projectId_ + "|series|" + resourceId_;
+
+      case Orthanc::ResourceType_Instance:
+        return projectId_ + "|instance|" + boost::lexical_cast<std::string>(frameNumber_) + "|" + resourceId_;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  AnnotationsWorkspaceId::AnnotationsWorkspaceId(const std::string& projectId,
+                                                 Orthanc::ResourceType level,
+                                                 const std::string& resourceId,
+                                                 unsigned int frameNumber) :
+    projectId_(projectId),
+    level_(level),
+    resourceId_(resourceId),
+    frameNumber_(frameNumber)
+  {
+    if (level_ != Orthanc::ResourceType_Series &&
+        level_ != Orthanc::ResourceType_Instance)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    // The pipe symbol is disallowed as it is used to build the key, cf. GetKey()
+    if (!Orthanc::Toolbox::IsAsciiString(projectId_) ||
+        projectId_.find('|') != std::string::npos)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                      "Project name containing non-ASCII characters or the pipe symbol: " + projectId_);
+    }
+
+    if (!Orthanc::Toolbox::IsAsciiString(resourceId_) ||
+        resourceId_.find('|') != std::string::npos)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                      "Resource ID containing non-ASCII characters or the pipe symbol: " + resourceId_);
+    }
+  }
+
+
+  unsigned int AnnotationsWorkspaceId::GetFrameNumber() const
+  {
+    if (level_ == Orthanc::ResourceType_Instance)
+    {
+      return frameNumber_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  std::string AnnotationsWorkspaceId::GetInfoKey() const
+  {
+    return GetKeyPrefix() + "|info";
+  }
+
+
+  std::string AnnotationsWorkspaceId::GetSettingsKey(const UserId& user) const
+  {
+    return GetKeyPrefix() + "|settings|" + user.GetKey();
+  }
+
+
+  std::string AnnotationsWorkspaceId::GetFeaturesKey(const UserId& user) const
+  {
+    return GetKeyPrefix() + "|features|" + user.GetKey();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ViewerPlugin/Annotations/AnnotationsWorkspaceId.h	Tue Sep 01 21:24:14 2026 +0200
@@ -0,0 +1,72 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, 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 "UserId.h"
+
+#include <Enumerations.h>
+
+
+namespace OrthancWSI
+{
+  class AnnotationsWorkspaceId
+  {
+  private:
+    std::string            projectId_;
+    Orthanc::ResourceType  level_;
+    std::string            resourceId_;
+    unsigned int           frameNumber_;
+
+    std::string GetKeyPrefix() const;
+
+  public:
+    AnnotationsWorkspaceId(const std::string& projectId,
+                           Orthanc::ResourceType level,
+                           const std::string& resourceId,
+                           unsigned int frameNumber);
+
+    const std::string& GetProjectId() const
+    {
+      return projectId_;
+    }
+
+    Orthanc::ResourceType GetLevel() const
+    {
+      return level_;
+    }
+
+    const std::string& GetResourceId() const
+    {
+      return resourceId_;
+    }
+
+    unsigned int GetFrameNumber() const;
+
+    std::string GetInfoKey() const;
+
+    std::string GetSettingsKey(const UserId& user) const;
+
+    std::string GetFeaturesKey(const UserId& user) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ViewerPlugin/Annotations/ISerializable.cpp	Tue Sep 01 21:24:14 2026 +0200
@@ -0,0 +1,50 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, 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 "../../Framework/PrecompiledHeadersWSI.h"
+#include "ISerializable.h"
+
+#include "../ViewerToolbox.h"
+
+#include <Toolbox.h>
+
+
+namespace OrthancWSI
+{
+  void ISerializable::Serialize(std::string& serialized,
+                                const ISerializable& obj)
+  {
+    Json::Value value;
+    obj.Serialize(value);
+    Orthanc::Toolbox::WriteFastJson(serialized, value);
+  }
+
+
+  void ISerializable::SetKeyValueStore(const std::string& key,
+                                       const ISerializable& obj)
+  {
+    std::string s;
+    ISerializable::Serialize(s, obj);
+    ViewerToolbox::SetKeyValueStore(key, s);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ViewerPlugin/Annotations/ISerializable.h	Tue Sep 01 21:24:14 2026 +0200
@@ -0,0 +1,47 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, 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 <boost/noncopyable.hpp>
+#include <json/value.h>
+
+
+namespace OrthancWSI
+{
+  class ISerializable : public boost::noncopyable
+  {
+  public:
+    virtual ~ISerializable()
+    {
+    }
+
+    virtual void Serialize(Json::Value& serialized) const = 0;
+
+    static void Serialize(std::string& serialized,
+                          const ISerializable& obj);
+
+    static void SetKeyValueStore(const std::string& key,
+                                 const ISerializable& obj);
+  };
+}
--- a/ViewerPlugin/CMakeLists.txt	Tue Sep 01 21:05:06 2026 +0200
+++ b/ViewerPlugin/CMakeLists.txt	Tue Sep 01 21:24:14 2026 +0200
@@ -186,7 +186,9 @@
 
 set(ORTHANC_WSI_SOURCES
   Annotations/AnnotationsRestApi.cpp
+  Annotations/AnnotationsWorkspaceId.cpp
   Annotations/IAuthenticatedUser.cpp
+  Annotations/ISerializable.cpp
   Annotations/UserId.cpp
   DicomPyramidCache.cpp
   IIIF.cpp
--- a/ViewerPlugin/ViewerToolbox.cpp	Tue Sep 01 21:05:06 2026 +0200
+++ b/ViewerPlugin/ViewerToolbox.cpp	Tue Sep 01 21:24:14 2026 +0200
@@ -24,11 +24,15 @@
 #include "../Framework/PrecompiledHeadersWSI.h"
 #include "ViewerToolbox.h"
 
+#include <Logging.h>
 #include <Toolbox.h>
 
 #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h"
 
 
+static const char* const KEY_VALUE_STORE = "wsi";
+
+
 namespace OrthancWSI
 {
   namespace ViewerToolbox
@@ -47,5 +51,62 @@
       Json::Value answer = Json::objectValue;
       AnswerJson(output, answer);
     }
+
+
+    void SetKeyValueStore(const std::string& key,
+                          const std::string& value)
+    {
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 8)
+      OrthancPlugins::KeyValueStore store(KEY_VALUE_STORE);
+      store.Store(key, value);
+#else
+      LOG(WARNING) << "Your Orthanc SDK is too old to save annotations";
+#endif
+    }
+
+
+    void SetKeyValueStore(const std::string& key,
+                          const Json::Value& value)
+    {
+      std::string s;
+      Orthanc::Toolbox::WriteFastJson(s, value);
+      SetKeyValueStore(key, s);
+    }
+
+
+    bool LookupKeyValueStore(std::string& value,
+                             const std::string& key)
+    {
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 8)
+      OrthancPlugins::KeyValueStore store(KEY_VALUE_STORE);
+      return store.GetValue(value, key);
+#else
+      LOG(WARNING) << "Your Orthanc SDK is too old to load annotations";
+      return false;
+#endif
+    }
+
+
+    bool LookupKeyValueStore(Json::Value& value,
+                             const std::string& key)
+    {
+      std::string s;
+      if (LookupKeyValueStore(s, key))
+      {
+        if (Orthanc::Toolbox::ReadJson(value, s))
+        {
+          return true;
+        }
+        else
+        {
+          LOG(WARNING) << "Discarding incorrect JSON in the key-value store: " << key;
+          return false;
+        }
+      }
+      else
+      {
+        return false;
+      }
+    }
   }
 }
--- a/ViewerPlugin/ViewerToolbox.h	Tue Sep 01 21:05:06 2026 +0200
+++ b/ViewerPlugin/ViewerToolbox.h	Tue Sep 01 21:24:14 2026 +0200
@@ -35,5 +35,17 @@
                     const Json::Value& value);
 
     void AnswerEmpty(OrthancPluginRestOutput* output);
+
+    void SetKeyValueStore(const std::string& key,
+                          const std::string& value);
+
+    void SetKeyValueStore(const std::string& key,
+                          const Json::Value& value);
+
+    bool LookupKeyValueStore(std::string& value,
+                             const std::string& key);
+
+    bool LookupKeyValueStore(Json::Value& value,
+                             const std::string& key);
   }
 }