# HG changeset patch # User Alain Mazy # Date 1744883282 -7200 # Node ID a3801ea807349de204cbc40b4d87c90f1de87db1 # Parent 6e165e40b1dfbe2ac1d99778056a08dbb435a57c basic thumbnail implementation diff -r 6e165e40b1df -r a3801ea80734 CMakeLists.txt --- 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 ) diff -r 6e165e40b1df -r a3801ea80734 NEWS --- 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) diff -r 6e165e40b1df -r a3801ea80734 Plugin/Plugin.cpp --- 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 +void RetrieveInstanceRenderedThumbnail(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + RetrieveInstanceRendered(output, url, request, isThumbnail); +} + +template +void RetrieveFrameRenderedThumbnail(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + RetrieveFrameRendered(output, url, request, isThumbnail); +} + +template +void RetrieveSeriesRenderedThumbnail(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + RetrieveSeriesRendered(output, url, request, isThumbnail); +} + +template +void RetrieveStudyRenderedThumbnail(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + RetrieveStudyRendered(output, url, request, isThumbnail); +} + extern "C" @@ -662,10 +694,15 @@ OrthancPlugins::RegisterRestCallback(root + "info", true); - OrthancPlugins::RegisterRestCallback(root + "studies/([^/]*)/rendered", true); - OrthancPlugins::RegisterRestCallback(root + "studies/([^/]*)/series/([^/]*)/rendered", true); - OrthancPlugins::RegisterRestCallback(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/rendered", true); - OrthancPlugins::RegisterRestCallback(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/frames/([^/]*)/rendered", true); + OrthancPlugins::RegisterRestCallback >(root + "studies/([^/]*)/rendered", true); + OrthancPlugins::RegisterRestCallback >(root + "studies/([^/]*)/series/([^/]*)/rendered", true); + OrthancPlugins::RegisterRestCallback >(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/rendered", true); + OrthancPlugins::RegisterRestCallback >(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/frames/([^/]*)/rendered", true); + + OrthancPlugins::RegisterRestCallback >(root + "studies/([^/]*)/thumbnail", true); + OrthancPlugins::RegisterRestCallback >(root + "studies/([^/]*)/series/([^/]*)/thumbnail", true); + OrthancPlugins::RegisterRestCallback >(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/thumbnail", true); + OrthancPlugins::RegisterRestCallback >(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/frames/([^/]*)/thumbnail", true); if (!OrthancPlugins::Configuration::IsReadOnly()) { diff -r 6e165e40b1df -r a3801ea80734 Plugin/WadoRs.h --- 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); diff -r 6e165e40b1df -r a3801ea80734 Plugin/WadoRsRetrieveRendered.cpp --- 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 #include #include +#include + #if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 9, 7) # include @@ -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(frame), request); + AnswerFrameRendered(output, boost::lexical_cast(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 } diff -r 6e165e40b1df -r a3801ea80734 Resources/Images/video-thumbnail.jpg Binary file Resources/Images/video-thumbnail.jpg has changed diff -r 6e165e40b1df -r a3801ea80734 TODO --- 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