changeset 697:a3801ea80734

basic thumbnail implementation
author Alain Mazy <am@orthanc.team>
date Thu, 17 Apr 2025 11:48:02 +0200
parents 6e165e40b1df
children d55bf1f01495
files CMakeLists.txt NEWS Plugin/Plugin.cpp Plugin/WadoRs.h Plugin/WadoRsRetrieveRendered.cpp Resources/Images/video-thumbnail.jpg TODO
diffstat 7 files changed, 90 insertions(+), 24 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Thu Apr 17 09:59:46 2025 +0200
+++ b/CMakeLists.txt	Thu Apr 17 11:48:02 2025 +0200
@@ -157,6 +157,7 @@
   --no-upcase-check
   ${ADDITIONAL_RESOURCES}
   JAVASCRIPT_LIBS   ${JAVASCRIPT_LIBS_DIR}
+  VIDEO_THUMBNAIL   ${CMAKE_SOURCE_DIR}/Resources/Images/video-thumbnail.jpg
   )
 
 
--- a/NEWS	Thu Apr 17 09:59:46 2025 +0200
+++ b/NEWS	Thu Apr 17 11:48:02 2025 +0200
@@ -6,6 +6,9 @@
 
 * If calling /rendered route on a video, the plugin will now return the video file (MP4 or ...).
   This notably enables display of videos in OHIF 3.10.1.
+* Added basic support for thumbnails (aka sup 203: https://www.dicomstandard.org/docs/librariesprovider2/dicomdocuments/news/progress/docs/sups/sup203.pdf).
+  Right now, /thumbnail is equivalent to /rendered except for videos for which the same default video is shown since we are not able to
+  extract a thumbnail from a video.  This notably enables display of a video icon in the series list of OHIF 3.10.1.
 
 
 Version 1.18 (2024-12-18)
--- a/Plugin/Plugin.cpp	Thu Apr 17 09:59:46 2025 +0200
+++ b/Plugin/Plugin.cpp	Thu Apr 17 11:48:02 2025 +0200
@@ -528,6 +528,38 @@
   return OrthancPluginErrorCode_Success;
 }
 
+template <bool isThumbnail>
+void RetrieveInstanceRenderedThumbnail(OrthancPluginRestOutput* output,
+                                       const char* url,
+                                       const OrthancPluginHttpRequest* request)
+{
+  RetrieveInstanceRendered(output, url, request, isThumbnail);
+}
+
+template <bool isThumbnail>
+void RetrieveFrameRenderedThumbnail(OrthancPluginRestOutput* output,
+                                    const char* url,
+                                    const OrthancPluginHttpRequest* request)
+{
+  RetrieveFrameRendered(output, url, request, isThumbnail);
+}
+
+template <bool isThumbnail>
+void RetrieveSeriesRenderedThumbnail(OrthancPluginRestOutput* output,
+                                     const char* url,
+                                     const OrthancPluginHttpRequest* request)
+{
+  RetrieveSeriesRendered(output, url, request, isThumbnail);
+}
+
+template <bool isThumbnail>
+void RetrieveStudyRenderedThumbnail(OrthancPluginRestOutput* output,
+                                    const char* url,
+                                    const OrthancPluginHttpRequest* request)
+{
+  RetrieveStudyRendered(output, url, request, isThumbnail);
+}
+
 
 
 extern "C"
@@ -662,10 +694,15 @@
 
         OrthancPlugins::RegisterRestCallback<GetClientInformation>(root + "info", true);
 
-        OrthancPlugins::RegisterRestCallback<RetrieveStudyRendered>(root + "studies/([^/]*)/rendered", true);
-        OrthancPlugins::RegisterRestCallback<RetrieveSeriesRendered>(root + "studies/([^/]*)/series/([^/]*)/rendered", true);
-        OrthancPlugins::RegisterRestCallback<RetrieveInstanceRendered>(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/rendered", true);
-        OrthancPlugins::RegisterRestCallback<RetrieveFrameRendered>(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/frames/([^/]*)/rendered", true);
+        OrthancPlugins::RegisterRestCallback<RetrieveStudyRenderedThumbnail<false> >(root + "studies/([^/]*)/rendered", true);
+        OrthancPlugins::RegisterRestCallback<RetrieveSeriesRenderedThumbnail<false> >(root + "studies/([^/]*)/series/([^/]*)/rendered", true);
+        OrthancPlugins::RegisterRestCallback<RetrieveInstanceRenderedThumbnail<false> >(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/rendered", true);
+        OrthancPlugins::RegisterRestCallback<RetrieveFrameRenderedThumbnail<false> >(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/frames/([^/]*)/rendered", true);
+
+        OrthancPlugins::RegisterRestCallback<RetrieveStudyRenderedThumbnail<true> >(root + "studies/([^/]*)/thumbnail", true);
+        OrthancPlugins::RegisterRestCallback<RetrieveSeriesRenderedThumbnail<true> >(root + "studies/([^/]*)/series/([^/]*)/thumbnail", true);
+        OrthancPlugins::RegisterRestCallback<RetrieveInstanceRenderedThumbnail<true> >(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/thumbnail", true);
+        OrthancPlugins::RegisterRestCallback<RetrieveFrameRenderedThumbnail<true> >(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/frames/([^/]*)/thumbnail", true);
 
         if (!OrthancPlugins::Configuration::IsReadOnly())
         {
--- a/Plugin/WadoRs.h	Thu Apr 17 09:59:46 2025 +0200
+++ b/Plugin/WadoRs.h	Thu Apr 17 11:48:02 2025 +0200
@@ -96,19 +96,23 @@
 
 void RetrieveInstanceRendered(OrthancPluginRestOutput* output,
                               const char* url,
-                              const OrthancPluginHttpRequest* request);
+                              const OrthancPluginHttpRequest* request,
+                              bool isThumbnail);
 
 void RetrieveFrameRendered(OrthancPluginRestOutput* output,
                            const char* url,
-                           const OrthancPluginHttpRequest* request);
+                           const OrthancPluginHttpRequest* request,
+                           bool isThumbnail);
 
 void RetrieveSeriesRendered(OrthancPluginRestOutput* output,
                             const char* url,
-                            const OrthancPluginHttpRequest* request);
+                            const OrthancPluginHttpRequest* request,
+                            bool isThumbnail);
 
 void RetrieveStudyRendered(OrthancPluginRestOutput* output,
                            const char* url,
-                           const OrthancPluginHttpRequest* request);
+                           const OrthancPluginHttpRequest* request,
+                           bool isThumbnail);
 
 void SetPluginCanDownloadTranscodedFile(bool enable);
 
--- a/Plugin/WadoRsRetrieveRendered.cpp	Thu Apr 17 09:59:46 2025 +0200
+++ b/Plugin/WadoRsRetrieveRendered.cpp	Thu Apr 17 11:48:02 2025 +0200
@@ -29,6 +29,8 @@
 #include <Images/ImageTraits.h>
 #include <Logging.h>
 #include <Toolbox.h>
+#include <EmbeddedResources.h>
+
 
 #if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 9, 7)
 #  include <SerializationToolbox.h>
@@ -824,7 +826,8 @@
                                 const std::string& instanceId,
                                 const std::string& transferSyntax,
                                 int frame,
-                                const OrthancPluginHttpRequest* request)
+                                const OrthancPluginHttpRequest* request,
+                                bool isThumbnail)  // for now, for every images except for videos, we consider that /rendered is equivalent to /thumbnail
 {
   // If the instance is a video, we shall provide the video file itself (MP4, ...)
   Orthanc::DicomTransferSyntax currentSyntax;
@@ -832,13 +835,24 @@
   {
     if (currentSyntax >= Orthanc::DicomTransferSyntax_MPEG2MainProfileAtMainLevel && currentSyntax <= Orthanc::DicomTransferSyntax_HEVCMain10ProfileLevel5_1)
     {
-      OrthancPlugins::RestApiClient apiClient;
-      apiClient.SetPath(std::string("/instances/") + instanceId + "/frames/0/raw");
-      if (apiClient.Execute())
+      if (isThumbnail)
       {
-        apiClient.Forward(OrthancPlugins::GetGlobalContext(), output);
+        // we are not able to extract a thumbnail from a video -> just return a camera icon
+        std::string videoThumbnail;
+        Orthanc::EmbeddedResources::GetFileResource(videoThumbnail, Orthanc::EmbeddedResources::VIDEO_THUMBNAIL);
+        OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, videoThumbnail.c_str(), videoThumbnail.size(), "image/jpeg");
         return;
       }
+      else
+      {
+        OrthancPlugins::RestApiClient apiClient;
+        apiClient.SetPath(std::string("/instances/") + instanceId + "/frames/0/raw");
+        if (apiClient.Execute())
+        {
+          apiClient.Forward(OrthancPlugins::GetGlobalContext(), output);
+          return;
+        }
+      }
     }
   }
 
@@ -986,7 +1000,8 @@
 
 static void AnswerFrameRendered(OrthancPluginRestOutput* output,
                                 int frame,
-                                const OrthancPluginHttpRequest* request)
+                                const OrthancPluginHttpRequest* request,
+                                bool isThumbnail)
 {
   if (request->method != OrthancPluginHttpMethod_Get)
   {
@@ -999,7 +1014,7 @@
 
     if (LocateInstance(output, instanceId, studyInstanceUid, seriesInstanceUid, sopInstanceUid, transferSyntax, request))
     {
-      AnswerFrameRendered(output, instanceId, transferSyntax, frame, request);
+      AnswerFrameRendered(output, instanceId, transferSyntax, frame, request, isThumbnail);
     }
     else
     {
@@ -1011,27 +1026,30 @@
 
 void RetrieveInstanceRendered(OrthancPluginRestOutput* output,
                               const char* url,
-                              const OrthancPluginHttpRequest* request)
+                              const OrthancPluginHttpRequest* request,
+                              bool isThumbnail)
 {
   assert(request->groupsCount == 3);
-  AnswerFrameRendered(output, 1 /* first frame */, request);
+  AnswerFrameRendered(output, 1 /* first frame */, request, isThumbnail);
 }
 
 
 void RetrieveFrameRendered(OrthancPluginRestOutput* output,
                            const char* url,
-                           const OrthancPluginHttpRequest* request)
+                           const OrthancPluginHttpRequest* request,
+                           bool isThumbnail)
 {
   assert(request->groupsCount == 4);
   const char* frame = request->groups[3];
 
-  AnswerFrameRendered(output, boost::lexical_cast<int>(frame), request);
+  AnswerFrameRendered(output, boost::lexical_cast<int>(frame), request, isThumbnail);
 }
 
 
 void RetrieveSeriesRendered(OrthancPluginRestOutput* output,
                             const char* url,
-                            const OrthancPluginHttpRequest* request)
+                            const OrthancPluginHttpRequest* request,
+                            bool isThumbnail)
 {
   assert(request->groupsCount == 2);
 
@@ -1044,7 +1062,7 @@
     std::string instanceOrthancId, studyInstanceUid, seriesInstanceUid, transferSyntax;
     if (LocateOneInstance(output, instanceOrthancId, studyInstanceUid, seriesInstanceUid, transferSyntax, request))
     {
-      AnswerFrameRendered(output, instanceOrthancId, transferSyntax, 1 /* first frame */, request);
+      AnswerFrameRendered(output, instanceOrthancId, transferSyntax, 1 /* first frame */, request, isThumbnail);
       return;  // Success
     }
 
@@ -1055,7 +1073,8 @@
 
 void RetrieveStudyRendered(OrthancPluginRestOutput* output,
                            const char* url,
-                           const OrthancPluginHttpRequest* request)
+                           const OrthancPluginHttpRequest* request,
+                           bool isThumbnail)
 {
   assert(request->groupsCount == 1);
 
@@ -1068,7 +1087,7 @@
     std::string instanceOrthancId, studyInstanceUid, seriesInstanceUid, transferSyntax;
     if (LocateOneInstance(output, instanceOrthancId, studyInstanceUid, seriesInstanceUid, transferSyntax, request))
     {
-      AnswerFrameRendered(output, instanceOrthancId, transferSyntax, 1 /* first frame */, request);
+      AnswerFrameRendered(output, instanceOrthancId, transferSyntax, 1 /* first frame */, request, isThumbnail);
       return;  // Success
     }
 
Binary file Resources/Images/video-thumbnail.jpg has changed
--- a/TODO	Thu Apr 17 09:59:46 2025 +0200
+++ b/TODO	Thu Apr 17 11:48:02 2025 +0200
@@ -31,7 +31,9 @@
 
 * Add support for application/zip in /dicom-web/studies/ (aka sup 211: https://www.dicomstandard.org/docs/librariesprovider2/dicomdocuments/news/ftsup/docs/sups/sup211.pdf?sfvrsn=9fe9edae_2)
 
-* Add support for thumbnails (aka sup 203: https://www.dicomstandard.org/docs/librariesprovider2/dicomdocuments/news/progress/docs/sups/sup203.pdf)
+* Add support for thumbnails (aka sup 203: https://www.dicomstandard.org/docs/librariesprovider2/dicomdocuments/news/progress/docs/sups/sup203.pdf).
+  Right now, thumbnail is equivalent to /rendered except for video thumbnails for which the same default video icon is shown since we are not able to
+  extract a thumbnail from a video.
 
 * Support private tags in search fields:
   https://discourse.orthanc-server.org/t/dicomweb-plugin-exception-of-unknown-dicom-tag-for-private-data-element-tags-while-using-query-parameters/3998