Mercurial > hg > orthanc-education
changeset 84:0c20ad09e34b
refactoring by introducing EducationToolbox
| author | Sebastien Jodogne <s.jodogne@gmail.com> |
|---|---|
| date | Thu, 30 Jul 2026 12:37:40 +0200 |
| parents | 95cb8629add4 |
| children | 6ac5d5c49a26 |
| files | CMakeLists.txt Sources/EducationRestApi.cpp Sources/EducationToolbox.cpp Sources/EducationToolbox.h Sources/LTI/LTIRoutes.cpp Sources/OrthancDatabase.cpp Sources/OrthancDatabase.h |
| diffstat | 7 files changed, 247 insertions(+), 183 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Fri Jun 19 12:27:54 2026 +0200 +++ b/CMakeLists.txt Thu Jul 30 12:37:40 2026 +0200 @@ -205,6 +205,7 @@ Sources/EducationConfiguration.cpp Sources/EducationEnumerations.cpp Sources/EducationRestApi.cpp + Sources/EducationToolbox.cpp Sources/HttpToolbox.cpp Sources/LTI/LTIContext.cpp Sources/LTI/LTIRoutes.cpp
--- a/Sources/EducationRestApi.cpp Fri Jun 19 12:27:54 2026 +0200 +++ b/Sources/EducationRestApi.cpp Thu Jul 30 12:37:40 2026 +0200 @@ -28,6 +28,7 @@ #include "Dicomization/DicomizationJob.h" #include "Dicomization/WholeSlideImagingDicomizer.h" #include "EducationConfiguration.h" +#include "EducationToolbox.h" #include "LTI/LTIRoutes.h" #include "OrthancDatabase.h" #include "ProjectPermissionContext.h" @@ -183,7 +184,7 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); } - const std::string viewerUrl = OrthancDatabase::GenerateViewerUrl(viewer, resource); + const std::string viewerUrl = EducationToolbox::GenerateViewerUrl(viewer, resource); Json::Value answer;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Sources/EducationToolbox.cpp Thu Jul 30 12:37:40 2026 +0200 @@ -0,0 +1,186 @@ +/** + * SPDX-FileCopyrightText: 2024-2026 Sebastien Jodogne, EPL UCLouvain, Belgium + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +/** + * Orthanc for Education + * Copyright (C) 2024-2026 Sebastien Jodogne, EPL 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 "EducationToolbox.h" + +#include <OrthancException.h> + + +static std::string FormatWSITitle(const std::string& title) +{ + if (title.empty()) + { + return ""; + } + else + { + /** + * The following implementation is correct if title is an UTF-8 + * string: "When a new URI scheme defines a component that + * represents textual data consisting of characters from the + * Universal Character Set [UCS], the data should first be encoded + * as octets according to the UTF-8 character encoding [STD63]; + * then only those octets that do not correspond to characters in + * the unreserved set should be percent- encoded." + * https://www.rfc-editor.org/rfc/rfc3986 + **/ + std::string tmp; + Orthanc::Toolbox::UriEncode(tmp, title); + return "&description=" + tmp; + } +} + + +namespace EducationToolbox +{ + std::string GenerateStudyViewerUrl(ViewerType viewer, + const std::string& studyId, + const std::string& studyInstanceUid, + const std::string& title) + { + switch (viewer) + { + case ViewerType_StoneWebViewer: + return "stone-webviewer/index.html?study=" + studyInstanceUid; + + case ViewerType_VolView: + return "volview/index.html?names=[archive.zip]&urls=[../studies/" + studyId + "/archive]"; + + case ViewerType_OHIF_Basic: + return "ohif/viewer?StudyInstanceUIDs=" + studyInstanceUid; + + case ViewerType_OHIF_VolumeRendering: + return "ohif/viewer?hangingprotocolId=mprAnd3DVolumeViewport&StudyInstanceUIDs=" + studyInstanceUid; + + case ViewerType_OHIF_TumorVolume: + return "ohif/tmtv?StudyInstanceUIDs=" + studyInstanceUid; + + case ViewerType_OHIF_Segmentation: + return "ohif/segmentation?StudyInstanceUIDs=" + studyInstanceUid; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + + std::string GenerateSeriesViewerUrl(ViewerType viewer, + const std::string& seriesId, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& title) + { + switch (viewer) + { + case ViewerType_StoneWebViewer: + return "stone-webviewer/index.html?study=" + studyInstanceUid + "&series=" + seriesInstanceUid; + + case ViewerType_WholeSlideImaging: + return "wsi/app/viewer.html?series=" + seriesId + FormatWSITitle(title); + + case ViewerType_VolView: + return "volview/index.html?names=[archive.zip]&urls=[../series/" + seriesId + "/archive]"; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + + std::string GenerateInstanceViewerUrl(ViewerType viewer, + const std::string& instanceId, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& sopInstanceUid, + const std::string& title) + { + switch (viewer) + { + case ViewerType_WholeSlideImaging: + return "wsi/app/viewer.html?instance=" + instanceId + FormatWSITitle(title); + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + + std::string GenerateViewerUrl(ViewerType viewer, + const std::map<std::string, std::string>& resource) + { + const std::string levelString = HttpToolbox::ReadMandatoryString(resource, "level"); + const Orthanc::ResourceType level = Orthanc::StringToResourceType(levelString.c_str()); + + switch (level) + { + case Orthanc::ResourceType_Study: + return GenerateStudyViewerUrl(viewer, + HttpToolbox::ReadMandatoryString(resource, "resource-id"), + HttpToolbox::ReadMandatoryString(resource, "study-instance-uid"), + HttpToolbox::ReadMandatoryString(resource, "title")); + + case Orthanc::ResourceType_Series: + return GenerateSeriesViewerUrl(viewer, + HttpToolbox::ReadMandatoryString(resource, "resource-id"), + HttpToolbox::ReadMandatoryString(resource, "study-instance-uid"), + HttpToolbox::ReadMandatoryString(resource, "series-instance-uid"), + HttpToolbox::ReadMandatoryString(resource, "title")); + + case Orthanc::ResourceType_Instance: + return GenerateInstanceViewerUrl(viewer, + HttpToolbox::ReadMandatoryString(resource, "resource-id"), + HttpToolbox::ReadMandatoryString(resource, "study-instance-uid"), + HttpToolbox::ReadMandatoryString(resource, "series-instance-uid"), + HttpToolbox::ReadMandatoryString(resource, "sop-instance-uid"), + HttpToolbox::ReadMandatoryString(resource, "title")); + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + + std::string GenerateViewerUrl(ViewerType viewer, + const Json::Value& resource) + { + std::map<std::string, std::string> args; + + if (resource.type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + Json::Value::Members members = resource.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + const Json::Value& value = resource[members[i]]; + if (value.type() == Json::stringValue) + { + args[members[i]] = value.asString(); + } + } + + return GenerateViewerUrl(viewer, args); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Sources/EducationToolbox.h Thu Jul 30 12:37:40 2026 +0200 @@ -0,0 +1,55 @@ +/** + * SPDX-FileCopyrightText: 2024-2026 Sebastien Jodogne, EPL UCLouvain, Belgium + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +/** + * Orthanc for Education + * Copyright (C) 2024-2026 Sebastien Jodogne, EPL 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 "Permissions/AuthenticatedUser.h" + + +namespace EducationToolbox +{ + std::string GenerateStudyViewerUrl(ViewerType viewer, + const std::string& studyId, + const std::string& studyInstanceUid, + const std::string& description); + + std::string GenerateSeriesViewerUrl(ViewerType viewer, + const std::string& seriesId, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& description); + + std::string GenerateInstanceViewerUrl(ViewerType viewer, + const std::string& instanceId, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& sopInstanceUid, + const std::string& description); + + std::string GenerateViewerUrl(ViewerType viewer, + const std::map<std::string, std::string>& resource); + + std::string GenerateViewerUrl(ViewerType viewer, + const Json::Value& resource); +}
--- a/Sources/LTI/LTIRoutes.cpp Fri Jun 19 12:27:54 2026 +0200 +++ b/Sources/LTI/LTIRoutes.cpp Thu Jul 30 12:37:40 2026 +0200 @@ -25,6 +25,7 @@ #include "LTIRoutes.h" #include "../EducationConfiguration.h" +#include "../EducationToolbox.h" #include "../HttpToolbox.h" #include "../OrthancDatabase.h" #include "../ProjectPermissionContext.h" @@ -353,7 +354,7 @@ const ViewerType viewer = ParseViewerType(HttpToolbox::ReadMandatoryString(args, "viewer")); link["title"] = HttpToolbox::ReadMandatoryString(args, "title"); - linkUrl = Orthanc::Toolbox::JoinUri("../..", OrthancDatabase::GenerateViewerUrl(viewer, args)); + linkUrl = Orthanc::Toolbox::JoinUri("../..", EducationToolbox::GenerateViewerUrl(viewer, args)); } else if (type == "project") { @@ -432,7 +433,7 @@ } const ViewerType viewer = ParseViewerType(HttpToolbox::ReadMandatoryString(args, "viewer")); - const std::string viewerUrl = Orthanc::Toolbox::JoinUri("../..", OrthancDatabase::GenerateViewerUrl(viewer, args)); + const std::string viewerUrl = Orthanc::Toolbox::JoinUri("../..", EducationToolbox::GenerateViewerUrl(viewer, args)); // We manually reimplement "OrthancPluginRedirect()", otherwise "Set-Cookie" has no effect OrthancPluginSetHttpHeader(OrthancPlugins::GetGlobalContext(), output, "Location", viewerUrl.c_str());
--- a/Sources/OrthancDatabase.cpp Fri Jun 19 12:27:54 2026 +0200 +++ b/Sources/OrthancDatabase.cpp Thu Jul 30 12:37:40 2026 +0200 @@ -435,164 +435,8 @@ } -static std::string FormatWSITitle(const std::string& title) -{ - if (title.empty()) - { - return ""; - } - else - { - /** - * The following implementation is correct if title is an UTF-8 - * string: "When a new URI scheme defines a component that - * represents textual data consisting of characters from the - * Universal Character Set [UCS], the data should first be encoded - * as octets according to the UTF-8 character encoding [STD63]; - * then only those octets that do not correspond to characters in - * the unreserved set should be percent- encoded." - * https://www.rfc-editor.org/rfc/rfc3986 - **/ - std::string tmp; - Orthanc::Toolbox::UriEncode(tmp, title); - return "&description=" + tmp; - } -} - - namespace OrthancDatabase { - std::string GenerateStudyViewerUrl(ViewerType viewer, - const std::string& studyId, - const std::string& studyInstanceUid, - const std::string& title) - { - switch (viewer) - { - case ViewerType_StoneWebViewer: - return "stone-webviewer/index.html?study=" + studyInstanceUid; - - case ViewerType_VolView: - return "volview/index.html?names=[archive.zip]&urls=[../studies/" + studyId + "/archive]"; - - case ViewerType_OHIF_Basic: - return "ohif/viewer?StudyInstanceUIDs=" + studyInstanceUid; - - case ViewerType_OHIF_VolumeRendering: - return "ohif/viewer?hangingprotocolId=mprAnd3DVolumeViewport&StudyInstanceUIDs=" + studyInstanceUid; - - case ViewerType_OHIF_TumorVolume: - return "ohif/tmtv?StudyInstanceUIDs=" + studyInstanceUid; - - case ViewerType_OHIF_Segmentation: - return "ohif/segmentation?StudyInstanceUIDs=" + studyInstanceUid; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - - - std::string GenerateSeriesViewerUrl(ViewerType viewer, - const std::string& seriesId, - const std::string& studyInstanceUid, - const std::string& seriesInstanceUid, - const std::string& title) - { - switch (viewer) - { - case ViewerType_StoneWebViewer: - return "stone-webviewer/index.html?study=" + studyInstanceUid + "&series=" + seriesInstanceUid; - - case ViewerType_WholeSlideImaging: - return "wsi/app/viewer.html?series=" + seriesId + FormatWSITitle(title); - - case ViewerType_VolView: - return "volview/index.html?names=[archive.zip]&urls=[../series/" + seriesId + "/archive]"; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - - - std::string GenerateInstanceViewerUrl(ViewerType viewer, - const std::string& instanceId, - const std::string& studyInstanceUid, - const std::string& seriesInstanceUid, - const std::string& sopInstanceUid, - const std::string& title) - { - switch (viewer) - { - case ViewerType_WholeSlideImaging: - return "wsi/app/viewer.html?instance=" + instanceId + FormatWSITitle(title); - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - - - std::string GenerateViewerUrl(ViewerType viewer, - const std::map<std::string, std::string>& resource) - { - const std::string levelString = HttpToolbox::ReadMandatoryString(resource, "level"); - const Orthanc::ResourceType level = Orthanc::StringToResourceType(levelString.c_str()); - - switch (level) - { - case Orthanc::ResourceType_Study: - return GenerateStudyViewerUrl(viewer, - HttpToolbox::ReadMandatoryString(resource, "resource-id"), - HttpToolbox::ReadMandatoryString(resource, "study-instance-uid"), - HttpToolbox::ReadMandatoryString(resource, "title")); - - case Orthanc::ResourceType_Series: - return GenerateSeriesViewerUrl(viewer, - HttpToolbox::ReadMandatoryString(resource, "resource-id"), - HttpToolbox::ReadMandatoryString(resource, "study-instance-uid"), - HttpToolbox::ReadMandatoryString(resource, "series-instance-uid"), - HttpToolbox::ReadMandatoryString(resource, "title")); - - case Orthanc::ResourceType_Instance: - return GenerateInstanceViewerUrl(viewer, - HttpToolbox::ReadMandatoryString(resource, "resource-id"), - HttpToolbox::ReadMandatoryString(resource, "study-instance-uid"), - HttpToolbox::ReadMandatoryString(resource, "series-instance-uid"), - HttpToolbox::ReadMandatoryString(resource, "sop-instance-uid"), - HttpToolbox::ReadMandatoryString(resource, "title")); - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - - - std::string GenerateViewerUrl(ViewerType viewer, - const Json::Value& resource) - { - std::map<std::string, std::string> args; - - if (resource.type() != Json::objectValue) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - Json::Value::Members members = resource.getMemberNames(); - for (size_t i = 0; i < members.size(); i++) - { - const Json::Value& value = resource[members[i]]; - if (value.type() == Json::stringValue) - { - args[members[i]] = value.asString(); - } - } - - return GenerateViewerUrl(viewer, args); - } - - void ListAllStudies(Json::Value& target) { target = Json::arrayValue;
--- a/Sources/OrthancDatabase.h Fri Jun 19 12:27:54 2026 +0200 +++ b/Sources/OrthancDatabase.h Thu Jul 30 12:37:40 2026 +0200 @@ -52,30 +52,6 @@ virtual bool HasAccessToSomeProject(const std::set<std::string>& projectIds) const = 0; }; - std::string GenerateStudyViewerUrl(ViewerType viewer, - const std::string& studyId, - const std::string& studyInstanceUid, - const std::string& description); - - std::string GenerateSeriesViewerUrl(ViewerType viewer, - const std::string& seriesId, - const std::string& studyInstanceUid, - const std::string& seriesInstanceUid, - const std::string& description); - - std::string GenerateInstanceViewerUrl(ViewerType viewer, - const std::string& instanceId, - const std::string& studyInstanceUid, - const std::string& seriesInstanceUid, - const std::string& sopInstanceUid, - const std::string& description); - - std::string GenerateViewerUrl(ViewerType viewer, - const std::map<std::string, std::string>& resource); - - std::string GenerateViewerUrl(ViewerType viewer, - const Json::Value& resource); - void ListAllStudies(Json::Value& target); void ListUnusedStudies(Json::Value& target,
