changeset 572:3efdbcfe34ab annotations

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 01 Sep 2026 22:14:57 +0200
parents c4f3e08e0c44
children bcf6c5f43f1d
files ViewerPlugin/Annotations/AnnotationsRestApi.cpp ViewerPlugin/Annotations/ImportedLayer.cpp ViewerPlugin/Annotations/ImportedLayer.h ViewerPlugin/Annotations/ProjectInformation.cpp ViewerPlugin/Annotations/ProjectInformation.h ViewerPlugin/Annotations/UserLayer.cpp ViewerPlugin/Annotations/UserLayer.h ViewerPlugin/CMakeLists.txt
diffstat 8 files changed, 532 insertions(+), 273 deletions(-) [+]
line wrap: on
line diff
--- a/ViewerPlugin/Annotations/AnnotationsRestApi.cpp	Tue Sep 01 21:33:29 2026 +0200
+++ b/ViewerPlugin/Annotations/AnnotationsRestApi.cpp	Tue Sep 01 22:14:57 2026 +0200
@@ -24,14 +24,14 @@
 #include "../../Framework/PrecompiledHeadersWSI.h"
 #include "AnnotationsRestApi.h"
 
-#include "../../Framework/BackgroundColor.h"
 #include "../ViewerConfiguration.h"
 #include "../ViewerToolbox.h"
 #include "AnnotationsWorkspaceId.h"
 #include "IAuthenticatedUser.h"
-#include "ILayer.h"
-#include "ISerializable.h"
+#include "ImportedLayer.h"
 #include "LayersCollection.h"
+#include "ProjectInformation.h"
+#include "UserLayer.h"
 
 #include <Cache/SharedObjectCache.h>
 #include <Compression/GzipCompressor.h>
@@ -46,18 +46,14 @@
 
 namespace OrthancWSI
 {
-  static const char* const KEY_AUTHOR = "author";
-  static const char* const KEY_COLOR = "color";
   static const char* const KEY_FEATURES = "features";
   static const char* const KEY_ID = "id";
   static const char* const KEY_LAYERS = "layers";
   static const char* const KEY_LAYER_ID = "layer-id";
   static const char* const KEY_NAME = "name";
   static const char* const KEY_PUBLIC = "public";
-  static const char* const KEY_SHARED_WITH = "shared_with";
   static const char* const KEY_TYPE = "type";
   static const char* const KEY_VERSION = "version";
-  static const char* const KEY_VISIBLE = "visible";
   static const char* const KEY_ACTIVE_USERS = "active-users";
   static const char* const KEY_PROJECT_NAME = "project-name";
   static const char* const KEY_PROJECT_DESCRIPTION = "project-description";
@@ -65,204 +61,6 @@
   static const char* const KEY_IMPORTED_LAYERS = "imported-layers";
 
 
-  class UserLayer : public ILayer
-  {
-  private:
-    bool              isVisible_;
-    BackgroundColor   color_;
-    std::string       id_;
-    std::string       name_;
-    std::set<UserId>  sharedWith_;
-    bool              isPublic_;
-
-  public:
-    UserLayer(const BackgroundColor& color,
-              const std::string& name) :
-      isVisible_(true),
-      color_(color),
-      id_(Orthanc::Toolbox::GenerateUuid()),
-      name_(name),
-      isPublic_(false)
-    {
-    }
-
-    UserLayer(const Json::Value& serialized)
-    {
-      if (!serialized.isObject() ||
-          !serialized.isMember(KEY_SHARED_WITH) ||
-          !serialized[KEY_SHARED_WITH].isArray())
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-
-      isVisible_ = Orthanc::SerializationToolbox::ReadBoolean(serialized, KEY_VISIBLE);
-      color_ = BackgroundColor::FromHexadecimalString(Orthanc::SerializationToolbox::ReadString(serialized, KEY_COLOR));
-      id_ = Orthanc::SerializationToolbox::ReadString(serialized, KEY_ID);
-      name_ = Orthanc::SerializationToolbox::ReadString(serialized, KEY_NAME);
-      isPublic_ = Orthanc::SerializationToolbox::ReadBoolean(serialized, KEY_PUBLIC);
-
-      const Json::Value& v = serialized[KEY_SHARED_WITH];
-      for (Json::Value::ArrayIndex i = 0; i < v.size(); i++)
-      {
-        sharedWith_.insert(UserId(v[i]));
-      }
-    }
-
-    virtual std::string GetId() const ORTHANC_OVERRIDE
-    {
-      return id_;
-    }
-
-    bool IsVisible() const
-    {
-      return isVisible_;
-    }
-
-    const BackgroundColor& GetColor() const
-    {
-      return color_;
-    }
-
-    const std::string& GetName() const
-    {
-      return name_;
-    }
-
-    bool IsSharedWith(const UserId& user) const
-    {
-      assert(user.GetType() == UserId::Type_Root ||
-             user.GetType() == UserId::Type_Standard);
-
-      return (isPublic_ ||
-              user.GetType() == UserId::Type_Root ||
-              sharedWith_.find(user) != sharedWith_.end());
-    }
-
-    void Assign(const UserLayer& other)
-    {
-      if (other.GetId() != id_)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else
-      {
-        isVisible_ = other.isVisible_;
-        color_ = other.color_;
-        name_ = other.name_;
-        sharedWith_ = other.sharedWith_;
-        isPublic_ = other.isPublic_;
-      }
-    }
-
-    virtual void Serialize(Json::Value& serialized) const ORTHANC_OVERRIDE
-    {
-      Json::Value sharedWith = Json::arrayValue;
-      for (std::set<UserId>::const_iterator it = sharedWith_.begin(); it != sharedWith_.end(); ++it)
-      {
-        Json::Value item;
-        it->Serialize(item);
-        sharedWith.append(item);
-      }
-
-      serialized = Json::objectValue;
-      serialized[KEY_VISIBLE] = isVisible_;
-      serialized[KEY_COLOR] = color_.ToHexadecimalString();
-      serialized[KEY_ID] = id_;
-      serialized[KEY_NAME] = name_;
-      serialized[KEY_PUBLIC] = isPublic_;
-      serialized[KEY_SHARED_WITH] = sharedWith;
-    }
-  };
-
-
-  class ImportedLayer : public ILayer
-  {
-  private:
-    bool             isVisible_;
-    BackgroundColor  color_;
-    UserId           author_;
-    std::string      id_;
-    std::string      name_;
-
-  public:
-    ImportedLayer(const UserId& author,
-                  const UserLayer& layer) :
-      isVisible_(true),
-      color_(layer.GetColor()),
-      author_(author),
-      id_(layer.GetId()),
-      name_(layer.GetName())
-    {
-    }
-
-    ImportedLayer(const Json::Value& serialized)
-    {
-      if (!serialized.isObject() ||
-          !serialized.isMember(KEY_AUTHOR))
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-      }
-
-      isVisible_ = Orthanc::SerializationToolbox::ReadBoolean(serialized, KEY_VISIBLE);
-      author_ = UserId(serialized[KEY_AUTHOR]);
-      id_ = Orthanc::SerializationToolbox::ReadString(serialized, KEY_ID);
-      name_ = Orthanc::SerializationToolbox::ReadString(serialized, KEY_NAME);
-      color_ = BackgroundColor::FromHexadecimalString(Orthanc::SerializationToolbox::ReadString(serialized, KEY_COLOR));
-    }
-
-    void Assign(const ImportedLayer& other)
-    {
-      if (other.GetId() != id_)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else
-      {
-        isVisible_ = other.isVisible_;
-        color_ = other.color_;
-        author_ = other.author_;
-        name_ = other.name_;
-      }
-    }
-
-    virtual std::string GetId() const ORTHANC_OVERRIDE
-    {
-      return id_;
-    }
-
-    bool IsVisible() const
-    {
-      return isVisible_;
-    }
-
-    const BackgroundColor& GetColor() const
-    {
-      return color_;
-    }
-
-    const UserId& GetAuthor() const
-    {
-      return author_;
-    }
-
-    const std::string& GetName() const
-    {
-      return name_;
-    }
-
-    virtual void Serialize(Json::Value& serialized) const ORTHANC_OVERRIDE
-    {
-      serialized = Json::objectValue;
-      serialized[KEY_VISIBLE] = isVisible_;
-      serialized[KEY_COLOR] = color_.ToHexadecimalString();
-      serialized[KEY_ID] = id_;
-      serialized[KEY_NAME] = name_;
-
-      author_.Serialize(serialized[KEY_AUTHOR]);
-    }
-  };
-
-
   class UserAnnotationsSettings : public ISerializable
   {
   private:
@@ -399,74 +197,6 @@
   };
 
 
-  class ProjectInformation : public boost::noncopyable
-  {
-  private:
-    boost::mutex              mutex_;
-    std::string               projectId_;
-    boost::posix_time::ptime  lastUpdate_;
-    std::string               name_;
-    std::string               description_;
-
-    static boost::posix_time::ptime GetNow()
-    {
-      return boost::posix_time::second_clock::universal_time();
-    }
-
-    void Load()
-    {
-      Json::Value info;
-
-      if (OrthancPlugins::RestApiGet(info, "/education/api-plugins/project?id=" + projectId_, true) &&
-          info.isObject())
-      {
-        // The "orthanc-education" plugin is available
-        name_ = Orthanc::SerializationToolbox::ReadString(info, "name", "");
-        description_ = Orthanc::SerializationToolbox::ReadString(info, "description", "");
-      }
-      else
-      {
-        name_.clear();
-        description_.clear();
-      }
-
-      lastUpdate_ = GetNow();
-
-      description_ = boost::posix_time::to_iso_string(lastUpdate_);
-    }
-
-    // The mutex must be locked
-    void Refresh()
-    {
-      if (GetNow() - lastUpdate_ >= boost::posix_time::seconds(10))
-      {
-        Load();
-      }
-    }
-
-  public:
-    ProjectInformation(const std::string& projectId) :
-      projectId_(projectId)
-    {
-      Load();
-    }
-
-    std::string GetName()
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-      Refresh();
-      return name_;
-    }
-
-    std::string GetDescription()
-    {
-      boost::mutex::scoped_lock lock(mutex_);
-      Refresh();
-      return description_;
-    }
-  };
-
-
   class AnnotationsWorkspace : public Orthanc::IDynamicObject
   {
   private:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ViewerPlugin/Annotations/ImportedLayer.cpp	Tue Sep 01 22:14:57 2026 +0200
@@ -0,0 +1,95 @@
+/**
+ * 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 "ImportedLayer.h"
+
+#include "UserLayer.h"
+
+#include <OrthancException.h>
+#include <SerializationToolbox.h>
+
+
+static const char* const KEY_VISIBLE = "visible";
+static const char* const KEY_AUTHOR = "author";
+static const char* const KEY_COLOR = "color";
+static const char* const KEY_ID = "id";
+static const char* const KEY_NAME = "name";
+
+
+namespace OrthancWSI
+{
+  ImportedLayer::ImportedLayer(const UserId& author,
+                               const UserLayer& layer) :
+    isVisible_(true),
+    color_(layer.GetColor()),
+    author_(author),
+    id_(layer.GetId()),
+    name_(layer.GetName())
+  {
+  }
+
+
+  ImportedLayer::ImportedLayer(const Json::Value& serialized)
+  {
+    if (!serialized.isObject() ||
+        !serialized.isMember(KEY_AUTHOR))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    isVisible_ = Orthanc::SerializationToolbox::ReadBoolean(serialized, KEY_VISIBLE);
+    author_ = UserId(serialized[KEY_AUTHOR]);
+    id_ = Orthanc::SerializationToolbox::ReadString(serialized, KEY_ID);
+    name_ = Orthanc::SerializationToolbox::ReadString(serialized, KEY_NAME);
+    color_ = BackgroundColor::FromHexadecimalString(Orthanc::SerializationToolbox::ReadString(serialized, KEY_COLOR));
+  }
+
+
+  void ImportedLayer::Assign(const ImportedLayer& other)
+  {
+    if (other.GetId() != id_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      isVisible_ = other.isVisible_;
+      color_ = other.color_;
+      author_ = other.author_;
+      name_ = other.name_;
+    }
+  }
+
+
+  void ImportedLayer::Serialize(Json::Value& serialized) const
+  {
+    serialized = Json::objectValue;
+    serialized[KEY_VISIBLE] = isVisible_;
+    serialized[KEY_COLOR] = color_.ToHexadecimalString();
+    serialized[KEY_ID] = id_;
+    serialized[KEY_NAME] = name_;
+
+    author_.Serialize(serialized[KEY_AUTHOR]);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ViewerPlugin/Annotations/ImportedLayer.h	Tue Sep 01 22:14:57 2026 +0200
@@ -0,0 +1,81 @@
+/**
+ * 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 "../../Framework/BackgroundColor.h"
+#include "ILayer.h"
+#include "UserId.h"
+
+#include <Compatibility.h>
+
+
+namespace OrthancWSI
+{
+  class UserLayer;
+
+  class ImportedLayer : public ILayer
+  {
+  private:
+    bool             isVisible_;
+    BackgroundColor  color_;
+    UserId           author_;
+    std::string      id_;
+    std::string      name_;
+
+  public:
+    ImportedLayer(const UserId& author,
+                  const UserLayer& layer);
+
+    ImportedLayer(const Json::Value& serialized);
+
+    void Assign(const ImportedLayer& other);
+
+    virtual std::string GetId() const ORTHANC_OVERRIDE
+    {
+      return id_;
+    }
+
+    bool IsVisible() const
+    {
+      return isVisible_;
+    }
+
+    const BackgroundColor& GetColor() const
+    {
+      return color_;
+    }
+
+    const UserId& GetAuthor() const
+    {
+      return author_;
+    }
+
+    const std::string& GetName() const
+    {
+      return name_;
+    }
+
+    virtual void Serialize(Json::Value& serialized) const ORTHANC_OVERRIDE;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ViewerPlugin/Annotations/ProjectInformation.cpp	Tue Sep 01 22:14:57 2026 +0200
@@ -0,0 +1,93 @@
+/**
+ * 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 "ProjectInformation.h"
+
+#include "../../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h"
+
+#include <SerializationToolbox.h>
+
+
+static boost::posix_time::ptime GetNow()
+{
+  return boost::posix_time::second_clock::universal_time();
+}
+
+
+namespace OrthancWSI
+{
+  void ProjectInformation::Load()
+  {
+    Json::Value info;
+
+    if (OrthancPlugins::RestApiGet(info, "/education/api-plugins/project?id=" + projectId_, true) &&
+        info.isObject())
+    {
+      // The "orthanc-education" plugin is available
+      name_ = Orthanc::SerializationToolbox::ReadString(info, "name", "");
+      description_ = Orthanc::SerializationToolbox::ReadString(info, "description", "");
+    }
+    else
+    {
+      name_.clear();
+      description_.clear();
+    }
+
+    lastUpdate_ = GetNow();
+
+    description_ = boost::posix_time::to_iso_string(lastUpdate_);
+  }
+
+
+  void ProjectInformation::Refresh()
+  {
+    if (GetNow() - lastUpdate_ >= boost::posix_time::seconds(10))
+    {
+      Load();
+    }
+  }
+
+
+  ProjectInformation::ProjectInformation(const std::string& projectId) :
+    projectId_(projectId)
+  {
+    Load();
+  }
+
+
+  std::string ProjectInformation::GetName()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    Refresh();
+    return name_;
+  }
+
+
+  std::string ProjectInformation::GetDescription()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    Refresh();
+    return description_;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ViewerPlugin/Annotations/ProjectInformation.h	Tue Sep 01 22:14:57 2026 +0200
@@ -0,0 +1,54 @@
+/**
+ * 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/date_time/posix_time/posix_time.hpp>
+#include <boost/thread/mutex.hpp>
+#include <string>
+
+
+namespace OrthancWSI
+{
+  class ProjectInformation : public boost::noncopyable
+  {
+  private:
+    boost::mutex              mutex_;
+    std::string               projectId_;
+    boost::posix_time::ptime  lastUpdate_;
+    std::string               name_;
+    std::string               description_;
+
+    void Load();
+
+    // The mutex must be locked
+    void Refresh();
+
+  public:
+    ProjectInformation(const std::string& projectId);
+
+    std::string GetName();
+
+    std::string GetDescription();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ViewerPlugin/Annotations/UserLayer.cpp	Tue Sep 01 22:14:57 2026 +0200
@@ -0,0 +1,124 @@
+/**
+ * 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 "UserLayer.h"
+
+#include <OrthancException.h>
+#include <SerializationToolbox.h>
+#include <Toolbox.h>
+
+#include <cassert>
+
+
+static const char* const KEY_COLOR = "color";
+static const char* const KEY_ID = "id";
+static const char* const KEY_NAME = "name";
+static const char* const KEY_PUBLIC = "public";
+static const char* const KEY_SHARED_WITH = "shared_with";
+static const char* const KEY_VISIBLE = "visible";
+
+
+namespace OrthancWSI
+{
+  UserLayer::UserLayer(const BackgroundColor& color,
+                       const std::string& name) :
+    isVisible_(true),
+    color_(color),
+    id_(Orthanc::Toolbox::GenerateUuid()),
+    name_(name),
+    isPublic_(false)
+  {
+  }
+
+
+  UserLayer::UserLayer(const Json::Value& serialized)
+  {
+    if (!serialized.isObject() ||
+        !serialized.isMember(KEY_SHARED_WITH) ||
+        !serialized[KEY_SHARED_WITH].isArray())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    isVisible_ = Orthanc::SerializationToolbox::ReadBoolean(serialized, KEY_VISIBLE);
+    color_ = BackgroundColor::FromHexadecimalString(Orthanc::SerializationToolbox::ReadString(serialized, KEY_COLOR));
+    id_ = Orthanc::SerializationToolbox::ReadString(serialized, KEY_ID);
+    name_ = Orthanc::SerializationToolbox::ReadString(serialized, KEY_NAME);
+    isPublic_ = Orthanc::SerializationToolbox::ReadBoolean(serialized, KEY_PUBLIC);
+
+    const Json::Value& v = serialized[KEY_SHARED_WITH];
+    for (Json::Value::ArrayIndex i = 0; i < v.size(); i++)
+    {
+      sharedWith_.insert(UserId(v[i]));
+    }
+  }
+
+
+  bool UserLayer::IsSharedWith(const UserId& user) const
+  {
+    assert(user.GetType() == UserId::Type_Root ||
+           user.GetType() == UserId::Type_Standard);
+
+    return (isPublic_ ||
+            user.GetType() == UserId::Type_Root ||
+            sharedWith_.find(user) != sharedWith_.end());
+  }
+
+
+  void UserLayer::Assign(const UserLayer& other)
+  {
+    if (other.GetId() != id_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      isVisible_ = other.isVisible_;
+      color_ = other.color_;
+      name_ = other.name_;
+      sharedWith_ = other.sharedWith_;
+      isPublic_ = other.isPublic_;
+    }
+  }
+
+
+  void UserLayer::Serialize(Json::Value& serialized) const
+  {
+    Json::Value sharedWith = Json::arrayValue;
+    for (std::set<UserId>::const_iterator it = sharedWith_.begin(); it != sharedWith_.end(); ++it)
+    {
+      Json::Value item;
+      it->Serialize(item);
+      sharedWith.append(item);
+    }
+
+    serialized = Json::objectValue;
+    serialized[KEY_VISIBLE] = isVisible_;
+    serialized[KEY_COLOR] = color_.ToHexadecimalString();
+    serialized[KEY_ID] = id_;
+    serialized[KEY_NAME] = name_;
+    serialized[KEY_PUBLIC] = isPublic_;
+    serialized[KEY_SHARED_WITH] = sharedWith;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ViewerPlugin/Annotations/UserLayer.h	Tue Sep 01 22:14:57 2026 +0200
@@ -0,0 +1,79 @@
+/**
+ * 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 "../../Framework/BackgroundColor.h"
+#include "ILayer.h"
+#include "UserId.h"
+
+#include <Compatibility.h>
+
+#include <set>
+
+
+namespace OrthancWSI
+{
+  class UserLayer : public ILayer
+  {
+  private:
+    bool              isVisible_;
+    BackgroundColor   color_;
+    std::string       id_;
+    std::string       name_;
+    std::set<UserId>  sharedWith_;
+    bool              isPublic_;
+
+  public:
+    UserLayer(const BackgroundColor& color,
+              const std::string& name);
+
+    UserLayer(const Json::Value& serialized);
+
+    virtual std::string GetId() const ORTHANC_OVERRIDE
+    {
+      return id_;
+    }
+
+    bool IsVisible() const
+    {
+      return isVisible_;
+    }
+
+    const BackgroundColor& GetColor() const
+    {
+      return color_;
+    }
+
+    const std::string& GetName() const
+    {
+      return name_;
+    }
+
+    bool IsSharedWith(const UserId& user) const;
+
+    void Assign(const UserLayer& other);
+
+    virtual void Serialize(Json::Value& serialized) const ORTHANC_OVERRIDE;
+  };
+}
--- a/ViewerPlugin/CMakeLists.txt	Tue Sep 01 21:33:29 2026 +0200
+++ b/ViewerPlugin/CMakeLists.txt	Tue Sep 01 22:14:57 2026 +0200
@@ -189,8 +189,11 @@
   Annotations/AnnotationsWorkspaceId.cpp
   Annotations/IAuthenticatedUser.cpp
   Annotations/ISerializable.cpp
+  Annotations/ImportedLayer.cpp
   Annotations/LayersCollection.cpp
+  Annotations/ProjectInformation.cpp
   Annotations/UserId.cpp
+  Annotations/UserLayer.cpp
   DicomPyramidCache.cpp
   IIIF.cpp
   OrthancPluginConnection.cpp