Mercurial > hg > orthanc-wsi
changeset 573:bcf6c5f43f1d annotations
reorganization
| author | Sebastien Jodogne <s.jodogne@gmail.com> |
|---|---|
| date | Wed, 02 Sep 2026 16:09:00 +0200 |
| parents | 3efdbcfe34ab |
| children | 41f3ecfa81bd |
| files | ViewerPlugin/Annotations/AnnotationsRestApi.cpp ViewerPlugin/Annotations/UserAnnotationsSettings.cpp ViewerPlugin/Annotations/UserAnnotationsSettings.h ViewerPlugin/CMakeLists.txt |
| diffstat | 4 files changed, 229 insertions(+), 568 deletions(-) [+] |
line wrap: on
line diff
--- a/ViewerPlugin/Annotations/AnnotationsRestApi.cpp Tue Sep 01 22:14:57 2026 +0200 +++ b/ViewerPlugin/Annotations/AnnotationsRestApi.cpp Wed Sep 02 16:09:00 2026 +0200 @@ -26,23 +26,16 @@ #include "../ViewerConfiguration.h" #include "../ViewerToolbox.h" -#include "AnnotationsWorkspaceId.h" +#include "AnnotationsWorkspace.h" #include "IAuthenticatedUser.h" -#include "ImportedLayer.h" -#include "LayersCollection.h" -#include "ProjectInformation.h" -#include "UserLayer.h" #include <Cache/SharedObjectCache.h> #include <Compression/GzipCompressor.h> #include <Logging.h> -#include <MultiThreading/ReaderWriterLock.h> #include <SerializationToolbox.h> #include "../../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" -#include <boost/regex.hpp> - namespace OrthancWSI { @@ -54,568 +47,8 @@ static const char* const KEY_PUBLIC = "public"; static const char* const KEY_TYPE = "type"; static const char* const KEY_VERSION = "version"; - 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"; - static const char* const KEY_USER_LAYERS = "user-layers"; - static const char* const KEY_IMPORTED_LAYERS = "imported-layers"; - - - class UserAnnotationsSettings : public ISerializable - { - private: - LayersCollection userLayers_; - LayersCollection importedLayers_; - - public: - UserAnnotationsSettings() - { - } - - UserAnnotationsSettings(const Json::Value& serialized) - { - if (!serialized.isObject() || - !serialized.isMember(KEY_USER_LAYERS) || - !serialized.isMember(KEY_IMPORTED_LAYERS) || - !serialized[KEY_USER_LAYERS].isArray() || - !serialized[KEY_IMPORTED_LAYERS].isArray()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - else - { - const Json::Value& a = serialized[KEY_USER_LAYERS]; - for (Json::Value::ArrayIndex i = 0; i < a.size(); i++) - { - userLayers_.AddLayer(new UserLayer(a[i])); - } - - const Json::Value& b = serialized[KEY_IMPORTED_LAYERS]; - for (Json::Value::ArrayIndex i = 0; i < b.size(); i++) - { - importedLayers_.AddLayer(new ImportedLayer(b[i])); - } - } - } - - std::string AddUserLayer(UserLayer* layer) - { - std::unique_ptr<UserLayer> protection(layer); - - if (layer == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - - const std::string id = protection->GetId(); - - userLayers_.AddLayer(protection.release()); - - return id; - } - - UserLayer& GetUserLayer(const std::string& layerId) const - { - return dynamic_cast<UserLayer&>(userLayers_.GetLayer(layerId)); - } - - ImportedLayer& GetImportedLayer(const std::string& layerId) const - { - return dynamic_cast<ImportedLayer&>(importedLayers_.GetLayer(layerId)); - } - - LayersCollection& GetUserLayers() - { - return userLayers_; - } - - const LayersCollection& GetUserLayers() const - { - return userLayers_; - } - - LayersCollection& GetImportedLayers() - { - return importedLayers_; - } - - const LayersCollection& GetImportedLayers() const - { - return importedLayers_; - } - - std::string CreateUserLayer() - { - static const uint8_t PALETTE[] = { - 0xe6, 0x39, 0x46, // red: #e63946 - 0x2a, 0x9d, 0x8f, - 0xe9, 0xc4, 0x6a, - 0x26, 0x46, 0x53, - 0xf4, 0xa2, 0x61 - }; - - static const size_t PALETTE_SIZE = sizeof(PALETTE) / (3 * sizeof(uint8_t)); - - size_t item = userLayers_.GetSize() % PALETTE_SIZE; - - BackgroundColor color(PALETTE[3 * item], - PALETTE[3 * item + 1], - PALETTE[3 * item + 2]); - - std::string name; - if (userLayers_.GetSize() == 0) - { - name = "Default"; - } - else - { - name = "Layer " + boost::lexical_cast<std::string>(userLayers_.GetSize() + 1); - } - - return AddUserLayer(new UserLayer(color, name)); - } - - virtual void Serialize(Json::Value& serialized) const ORTHANC_OVERRIDE - { - serialized = Json::objectValue; - userLayers_.Serialize(serialized[KEY_USER_LAYERS]); - importedLayers_.Serialize(serialized[KEY_IMPORTED_LAYERS]); - } - - void ImportLayer(const UserId& author, - const UserLayer& layer) - { - if (importedLayers_.HasLayer(layer.GetId())) - { - LOG(INFO) << "Cannot re-import already imported layer: " << layer.GetId(); - } - else - { - importedLayers_.AddLayer(new ImportedLayer(author, layer)); - } - } - }; - - - class AnnotationsWorkspace : public Orthanc::IDynamicObject - { - private: - class PersistentInfo : public ISerializable - { - private: - std::set<UserId> activeUsers_; - - public: - PersistentInfo() - { - } - - PersistentInfo(const Json::Value& serialized) - { - const Json::Value& users = serialized[KEY_ACTIVE_USERS]; - - if (!users.isArray()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - for (Json::Value::ArrayIndex i = 0; i < users.size(); i++) - { - activeUsers_.insert(UserId(users[i])); - } - } - - // Return "true" iff. the user was not already tagged as active - bool AddActiveUser(const UserId& user) - { - if (activeUsers_.find(user) == activeUsers_.end()) - { - activeUsers_.insert(user); - return true; - } - else - { - return false; - } - } - - const std::set<UserId>& GetActiveUsers() const - { - return activeUsers_; - } - - virtual void Serialize(Json::Value& serialized) const ORTHANC_OVERRIDE - { - Json::Value users = Json::arrayValue; - for (std::set<UserId>::const_iterator it = activeUsers_.begin(); it != activeUsers_.end(); ++it) - { - Json::Value user; - it->Serialize(user); - users.append(user); - } - - serialized = Json::objectValue; - serialized[KEY_ACTIVE_USERS] = users; - } - }; - - - void Load(const UserId& user) - { - const std::string key = id_.GetSettingsKey(user); - - Json::Value layers; - if (ViewerToolbox::LookupKeyValueStore(layers, key)) - { - std::unique_ptr<UserAnnotationsSettings> item(new UserAnnotationsSettings(layers)); - - if (content_.find(user) == content_.end()) // Should never be false - { - content_[user] = item.release(); - } - } - } - - - typedef std::map<UserId, UserAnnotationsSettings*> Content; - - Orthanc::ReaderWriterLock mutex_; - AnnotationsWorkspaceId id_; - std::unique_ptr<PersistentInfo> persistentInfo_; - Content content_; - ProjectInformation projectInformation_; - - public: - AnnotationsWorkspace(const AnnotationsWorkspaceId& id) : - id_(id), - projectInformation_(id.GetProjectId()) - { - const std::string key = id.GetInfoKey(); - - Json::Value info; - - if (ViewerToolbox::LookupKeyValueStore(info, key)) - { - persistentInfo_.reset(new PersistentInfo(info)); - - for (std::set<UserId>::const_iterator it = persistentInfo_->GetActiveUsers().begin(); - it != persistentInfo_->GetActiveUsers().end(); ++it) - { - Load(*it); - } - } - else - { - persistentInfo_.reset(new PersistentInfo); - persistentInfo_->Serialize(info); - ViewerToolbox::SetKeyValueStore(key, info); - } - } - - ~AnnotationsWorkspace() - { - for (Content::iterator it = content_.begin(); it != content_.end(); ++it) - { - assert(it->second != NULL); - delete it->second; - } - } - - const AnnotationsWorkspaceId& GetId() const - { - return id_; - } - - std::string GetProjectName() - { - return projectInformation_.GetName(); - } - - std::string GetProjectDescription() - { - return projectInformation_.GetDescription(); - } - - - void SearchActiveUsers(std::set<UserId>& target, - const std::string& query) - { - Orthanc::ReaderWriterLock::ReadLock lock(mutex_); - - target.clear(); - - const boost::regex re(query); - const std::set<UserId>& activeUsers = persistentInfo_->GetActiveUsers(); - - for (std::set<UserId>::const_iterator it = activeUsers.begin(); it != activeUsers.end(); ++it) - { - if (boost::regex_search(it->GetName(), re)) - { - target.insert(*it); - } - } - } - - - void ListUsersSharingLayerWith(std::set<UserId>& target, - const UserId& user) - { - Orthanc::ReaderWriterLock::ReadLock lock(mutex_); - - target.clear(); - - // Loop over all the users in this workspace - for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) - { - assert(it->second != NULL); - if (!user.Equals(it->first)) // Don't add self - { - LayersCollection::Iterator iterator(it->second->GetUserLayers()); - - // Loop over all the user layers in this workspace - while (!iterator.IsDone()) - { - const UserLayer& layer = dynamic_cast<const UserLayer&>(iterator.GetLayer()); - - if (layer.IsSharedWith(user)) - { - target.insert(it->first); - break; - } - else - { - iterator.Next(); - } - } - } - } - } - - - class UserReader : public boost::noncopyable - { - private: - Orthanc::ReaderWriterLock::ReadLock lock_; - AnnotationsWorkspace& that_; - UserId userId_; - const UserAnnotationsSettings* userSettings_; - - public: - UserReader(AnnotationsWorkspace& that, - const UserId& userId) : - lock_(that.mutex_), - that_(that), - userId_(userId) - { - Content::const_iterator found = that.content_.find(userId); - - if (found == that.content_.end()) - { - userSettings_ = NULL; - } - else - { - assert(found->second != NULL); - userSettings_ = found->second; - } - } - - bool IsValid() const - { - return userSettings_ != NULL; - } - - void ListLayers(Json::Value& serialized) const - { - serialized = Json::objectValue; - - if (IsValid()) - { - userSettings_->Serialize(serialized); - } - else - { - serialized[KEY_USER_LAYERS] = Json::arrayValue; - serialized[KEY_IMPORTED_LAYERS] = Json::arrayValue; - } - } - - void ListLayersSharedWith(Json::Value& target, - const UserId& user) const - { - target = Json::arrayValue; - - if (IsValid()) - { - LayersCollection::Iterator iterator(userSettings_->GetUserLayers()); - - while (!iterator.IsDone()) - { - const UserLayer& layer = dynamic_cast<const UserLayer&>(iterator.GetLayer()); - - if (layer.IsSharedWith(user)) - { - Json::Value item; - layer.Serialize(item); - target.append(item); - } - - iterator.Next(); - } - } - } - - void ListImportedLayers(std::set<UserId>& authors, - std::set<std::string>& layerIds) const - { - authors.clear(); - layerIds.clear(); - - if (IsValid()) - { - LayersCollection::Iterator iterator(userSettings_->GetImportedLayers()); - - while (!iterator.IsDone()) - { - const ImportedLayer& layer = dynamic_cast<const ImportedLayer&>(iterator.GetLayer()); - - Content::const_iterator found = that_.content_.find(layer.GetAuthor()); - - if (found != that_.content_.end()) - { - assert(found->second != NULL); - - // Check that the layer has not been deleted in the meantime by its author, - // and that the layer is still shared with this user - if (found->second->GetUserLayers().HasLayer(layer.GetId())) - { - const UserLayer& authorLayer = found->second->GetUserLayer(layer.GetId()); - - if (authorLayer.IsSharedWith(userId_)) - { - authors.insert(layer.GetAuthor()); - layerIds.insert(layer.GetId()); - } - } - } - - iterator.Next(); - } - } - } - }; - - - class UserWriter : public boost::noncopyable - { - private: - Orthanc::ReaderWriterLock::WriteLock lock_; - AnnotationsWorkspace& that_; - UserId userId_; - UserAnnotationsSettings* userSettings_; - - void Commit() - { - ISerializable::SetKeyValueStore(that_.id_.GetSettingsKey(userId_), *userSettings_); - } - - public: - UserWriter(AnnotationsWorkspace& that, - const UserId& userId) : - lock_(that.mutex_), - that_(that), - userId_(userId) - { - if (that.persistentInfo_->AddActiveUser(userId_)) - { - // Only update the key-value store if this is the first time we meet this user - ISerializable::SetKeyValueStore(that.id_.GetInfoKey(), *that.persistentInfo_); - } - - Content::iterator found = that.content_.find(userId_); - - if (found == that.content_.end()) - { - std::unique_ptr<UserAnnotationsSettings> layers(new UserAnnotationsSettings); - userSettings_ = layers.get(); - that.content_[userId_] = layers.release(); - Commit(); - } - else - { - assert(found->second != NULL); - userSettings_ = found->second; - } - } - - void CreateUserLayer(Json::Value& answer) - { - assert(userSettings_ != NULL); - - const std::string layerId = userSettings_->CreateUserLayer(); - Commit(); - - UserLayer& layer = userSettings_->GetUserLayer(layerId); - layer.Serialize(answer); - } - - void UpdateUserLayer(const UserLayer& updated) - { - assert(userSettings_ != NULL); - - UserLayer& layer = userSettings_->GetUserLayer(updated.GetId()); - layer.Assign(updated); - Commit(); - } - - void DeleteUserLayer(const std::string& layerId) - { - assert(userSettings_ != NULL); - userSettings_->GetUserLayers().DeleteLayer(layerId); - Commit(); - } - - void ImportLayer(const UserId& author, - const std::string& layerId) - { - assert(userSettings_ != NULL); - - Content::const_iterator found = that_.content_.find(author); - if (found == that_.content_.end()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); - } - - assert(found->second != NULL); - const UserAnnotationsSettings& authorData = *found->second; - - UserLayer& layer = authorData.GetUserLayer(layerId); - if (!layer.IsSharedWith(userId_)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ForbiddenAccess); - } - - userSettings_->ImportLayer(author, layer); - Commit(); - } - - void RemoveImportedLayer(const std::string& layerId) - { - assert(userSettings_ != NULL); - userSettings_->GetImportedLayers().DeleteLayer(layerId); - Commit(); - } - - void UpdateImportedLayer(const ImportedLayer& updated) - { - assert(userSettings_ != NULL); - - ImportedLayer& layer = userSettings_->GetImportedLayer(updated.GetId()); - layer.Assign(updated); - Commit(); - } - }; - }; class CachedAnnotationsWorkspace : public boost::noncopyable
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ViewerPlugin/Annotations/UserAnnotationsSettings.cpp Wed Sep 02 16:09:00 2026 +0200 @@ -0,0 +1,147 @@ +/** + * 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 "UserAnnotationsSettings.h" + +#include <Logging.h> +#include <OrthancException.h> + +#include <boost/lexical_cast.hpp> + + +static const char* const KEY_USER_LAYERS = "user-layers"; +static const char* const KEY_IMPORTED_LAYERS = "imported-layers"; + + +namespace OrthancWSI +{ + UserAnnotationsSettings::UserAnnotationsSettings(const Json::Value& serialized) + { + if (!serialized.isObject() || + !serialized.isMember(KEY_USER_LAYERS) || + !serialized.isMember(KEY_IMPORTED_LAYERS) || + !serialized[KEY_USER_LAYERS].isArray() || + !serialized[KEY_IMPORTED_LAYERS].isArray()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + const Json::Value& a = serialized[KEY_USER_LAYERS]; + for (Json::Value::ArrayIndex i = 0; i < a.size(); i++) + { + userLayers_.AddLayer(new UserLayer(a[i])); + } + + const Json::Value& b = serialized[KEY_IMPORTED_LAYERS]; + for (Json::Value::ArrayIndex i = 0; i < b.size(); i++) + { + importedLayers_.AddLayer(new ImportedLayer(b[i])); + } + } + } + + + std::string UserAnnotationsSettings::AddUserLayer(UserLayer* layer) + { + std::unique_ptr<UserLayer> protection(layer); + + if (layer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + const std::string id = protection->GetId(); + + userLayers_.AddLayer(protection.release()); + + return id; + } + + + UserLayer& UserAnnotationsSettings::GetUserLayer(const std::string& layerId) const + { + return dynamic_cast<UserLayer&>(userLayers_.GetLayer(layerId)); + } + + + ImportedLayer& UserAnnotationsSettings::GetImportedLayer(const std::string& layerId) const + { + return dynamic_cast<ImportedLayer&>(importedLayers_.GetLayer(layerId)); + } + + + std::string UserAnnotationsSettings::CreateUserLayer() + { + static const uint8_t PALETTE[] = { + 0xe6, 0x39, 0x46, // red: #e63946 + 0x2a, 0x9d, 0x8f, + 0xe9, 0xc4, 0x6a, + 0x26, 0x46, 0x53, + 0xf4, 0xa2, 0x61 + }; + + static const size_t PALETTE_SIZE = sizeof(PALETTE) / (3 * sizeof(uint8_t)); + + size_t item = userLayers_.GetSize() % PALETTE_SIZE; + + BackgroundColor color(PALETTE[3 * item], + PALETTE[3 * item + 1], + PALETTE[3 * item + 2]); + + std::string name; + if (userLayers_.GetSize() == 0) + { + name = "Default"; + } + else + { + name = "Layer " + boost::lexical_cast<std::string>(userLayers_.GetSize() + 1); + } + + return AddUserLayer(new UserLayer(color, name)); + } + + + void UserAnnotationsSettings::Serialize(Json::Value& serialized) const + { + serialized = Json::objectValue; + userLayers_.Serialize(serialized[KEY_USER_LAYERS]); + importedLayers_.Serialize(serialized[KEY_IMPORTED_LAYERS]); + } + + + void UserAnnotationsSettings::ImportLayer(const UserId& author, + const UserLayer& layer) + { + if (importedLayers_.HasLayer(layer.GetId())) + { + LOG(INFO) << "Cannot re-import already imported layer: " << layer.GetId(); + } + else + { + importedLayers_.AddLayer(new ImportedLayer(author, layer)); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ViewerPlugin/Annotations/UserAnnotationsSettings.h Wed Sep 02 16:09:00 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 "LayersCollection.h" +#include "UserLayer.h" +#include "ImportedLayer.h" + + +namespace OrthancWSI +{ + class UserAnnotationsSettings : public ISerializable + { + private: + LayersCollection userLayers_; + LayersCollection importedLayers_; + + public: + UserAnnotationsSettings() + { + } + + UserAnnotationsSettings(const Json::Value& serialized); + + std::string AddUserLayer(UserLayer* layer); + + UserLayer& GetUserLayer(const std::string& layerId) const; + + ImportedLayer& GetImportedLayer(const std::string& layerId) const; + + LayersCollection& GetUserLayers() + { + return userLayers_; + } + + const LayersCollection& GetUserLayers() const + { + return userLayers_; + } + + LayersCollection& GetImportedLayers() + { + return importedLayers_; + } + + const LayersCollection& GetImportedLayers() const + { + return importedLayers_; + } + + std::string CreateUserLayer(); + + virtual void Serialize(Json::Value& serialized) const ORTHANC_OVERRIDE; + + void ImportLayer(const UserId& author, + const UserLayer& layer); + }; +}
--- a/ViewerPlugin/CMakeLists.txt Tue Sep 01 22:14:57 2026 +0200 +++ b/ViewerPlugin/CMakeLists.txt Wed Sep 02 16:09:00 2026 +0200 @@ -186,12 +186,14 @@ set(ORTHANC_WSI_SOURCES Annotations/AnnotationsRestApi.cpp + Annotations/AnnotationsWorkspace.cpp Annotations/AnnotationsWorkspaceId.cpp Annotations/IAuthenticatedUser.cpp Annotations/ISerializable.cpp Annotations/ImportedLayer.cpp Annotations/LayersCollection.cpp Annotations/ProjectInformation.cpp + Annotations/UserAnnotationsSettings.cpp Annotations/UserId.cpp Annotations/UserLayer.cpp DicomPyramidCache.cpp
