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,