# HG changeset patch # User Sebastien Jodogne # Date 1637753132 -3600 # Node ID 4d83d27a955ea0630251ef8d730159ddc241a133 # Parent 35ac2be493e2e424bb3904e234faf7e9f8cc540a# Parent ff03eb63e84731ba94ffcc7fba9afc5a2d16f6c2 merge diff -r 35ac2be493e2 -r 4d83d27a955e .hgignore --- a/.hgignore Wed Nov 24 12:25:19 2021 +0100 +++ b/.hgignore Wed Nov 24 12:25:32 2021 +0100 @@ -22,6 +22,7 @@ Applications/Samples/Deprecated/WebAssembly/ThirdPartyDownloads/ Applications/Samples/Deprecated/WebAssembly/installDir/ StoneWebViewer/Plugin/ThirdPartyDownloads/ +StoneWebViewer/Resources/package-lock.json StoneWebViewer/WebAssembly/ThirdPartyDownloads/ UnitTestsSources/ThirdPartyDownloads/ node_modules/ diff -r 35ac2be493e2 -r 4d83d27a955e Applications/Samples/RtViewerPlugin/CMakeLists.txt --- a/Applications/Samples/RtViewerPlugin/CMakeLists.txt Wed Nov 24 12:25:19 2021 +0100 +++ b/Applications/Samples/RtViewerPlugin/CMakeLists.txt Wed Nov 24 12:25:32 2021 +0100 @@ -27,7 +27,7 @@ set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") else() - set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.9.4") + set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.9.7") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") endif() diff -r 35ac2be493e2 -r 4d83d27a955e Applications/Samples/WebAssembly/CMakeLists.txt --- a/Applications/Samples/WebAssembly/CMakeLists.txt Wed Nov 24 12:25:19 2021 +0100 +++ b/Applications/Samples/WebAssembly/CMakeLists.txt Wed Nov 24 12:25:32 2021 +0100 @@ -28,7 +28,7 @@ set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") else() - set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.9.4") + set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.9.7") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") endif() diff -r 35ac2be493e2 -r 4d83d27a955e Applications/StoneWebViewer/BuildInstructions.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/BuildInstructions.txt Wed Nov 24 12:25:32 2021 +0100 @@ -0,0 +1,73 @@ +Build & develop instructions +============================ + +Prerequisites: +------------- + +- Install the usual build tools to build an Orthanc plugin (a good reference is the orthanc base image Dockerfile): https://github.com/jodogne/OrthancDocker/blob/master/base/Dockerfile +- Install EMSDK (a good reference is the wasm-builder image Dockerfile): https://github.com/jodogne/OrthancDocker/tree/master/wasm-builder + +Create your build environment: +----------------------------- + +mkdir ~/dev +cd ~/dev +hg clone https://hg.orthanc-server.com/orthanc/ +hg clone https://hg.orthanc-server.com/orthanc-stone/ +hg clone https://hg.orthanc-server.com/orthanc-dicomweb/ +mkdir -p ~/dev/build/orthanc +mkdir -p ~/dev/build/orthanc-dicomweb +mkdir -p ~/dev/build/wasm-stone-viewer +mkdir -p ~/dev/build/stone-viewer-plugin + +# build orthanc +cd ~/dev/build/orthanc +cmake -DCMAKE_BUILD_TYPE=Release -DUSE_SYSTEM_CIVETWEB=OFF -DALLOW_DOWNLOADS=ON ../../orthanc/OrthancServer +make -j 6 + +# build orthanc-dicomweb +cd ~/dev/build/orthanc-dicomweb +cmake -DCMAKE_BUILD_TYPE=Release -DSTATIC_BUILD -DALLOW_DOWNLOADS=ON ../../orthanc-dicomweb +make -j 6 + +# build the StoneViewer WASM code +# note: for fast link time: use -DCMAKE_BUILD_TYPE=RelWithDebInfo. However, this produces an output that is too large to be embedded in the plugin +# therefore, when you build the plugin, build the WASM code with -DCMAKE_BUILD_TYPE=Release +cd ~/dev/build/wasm-stone-viewer +cmake ../../orthanc-stone/Applications/StoneWebViewer/WebAssembly -DLIBCLANG=/usr/lib/x86_64-linux-gnu/libclang-10.so -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DALLOW_DOWNLOADS=ON -G Ninja +ninja install + +# build the StoneViewer Plugin +cd ~/dev/build/stone-viewer-plugin +cmake ../../orthanc-stone/Applications/StoneWebViewer/Plugin -DUSE -DCMAKE_BUILD_TYPE=Release -DSTATIC_BUILD=ON -DALLOW_DOWNLOADS=ON +make -j 6 + + +Updating the code: +----------------- + +To avoid rebuilding the plugin everytime you update the HTML/CSS/JS or C++ code from the WebAssembly, you can use the ServeFolders plugin +to host this part of the code. Here's a sample configuration file that works well for StoneViewer development: + +{ + "AuthenticationEnabled": false, + "HttpPort": 8043, + "Plugins": [ + "/home/alain/dev/build/stone-viewer-plugin/libStoneWebViewer.so", + "/home/alain/dev/build/orthanc-dicomweb/libOrthancDicomWeb.so", + "/home/alain/dev/build/orthanc/libServeFolders.so" + ], + + "ServeFolders": { + "/stone-webviewer-live" : "/home/alain/o/orthanc-stone/wasm-binaries/StoneWebViewer/" + } +} + +Everytime you modify the HTML/CSS/JS or C++ code from the WebAssembly, you don't need to restart Orthanc and can simply run +cd ~/dev/build/wasm-stone-viewer && ninja install +Then, the viewer is available on http://localhost:8043/stone-webviewer-live/index.html?study=1.2.3.4.5 + +If you modify the plugin code, you must of course rebuild the plugin and restart Orthanc + +If you modify the scss files, you'll need to run: +- Applications/StoneWebViewer/Resources$ ./node_modules/node-sass/bin/node-sass ./Styles/styles.scss > ../WebApplication/app.css as explained in Applications/StoneWebViewer/Resources/Styles.txt \ No newline at end of file diff -r 35ac2be493e2 -r 4d83d27a955e Applications/StoneWebViewer/NEWS --- a/Applications/StoneWebViewer/NEWS Wed Nov 24 12:25:19 2021 +0100 +++ b/Applications/StoneWebViewer/NEWS Wed Nov 24 12:25:32 2021 +0100 @@ -1,6 +1,21 @@ Pending changes in the mainline =============================== +* SeriesList: + - display the SeriesNumber tag in front of image count. + - order series by SeriesNumber + - don't show non displayable series (see "SkipSeriesFromModalities") +* In the top right overlay, display ContentDate/ContentTime if they are + available in the instance. If not, StudyDate is displayed (previous + behavior) +* New configuration options: + - "TimeFormat" to control the way Dicom Times are displayed. + - "SkipSeriesFromModalities" to ignore series from given modality types. + + +Version 2.2 (2021-08-31) +======================== + * Support detection of windowing and rescale in Philips multiframe images * Fix values reported in "ww/wc" info panel (windowing width and center) diff -r 35ac2be493e2 -r 4d83d27a955e Applications/StoneWebViewer/Resources/Styles/_serieslist.scss --- a/Applications/StoneWebViewer/Resources/Styles/_serieslist.scss Wed Nov 24 12:25:19 2021 +0100 +++ b/Applications/StoneWebViewer/Resources/Styles/_serieslist.scss Wed Nov 24 12:25:32 2021 +0100 @@ -80,11 +80,14 @@ line-height:15px; width:15px; height:15px; - border-radius: 100%; + border-radius: 5px; background-color: $gray; vertical-align: middle; text-align: center; font-weight: bold; + width: max-content; + padding: 1px 5px; + line-height: 12px; } .wvSerieslist__information{ font-size: 14px; diff -r 35ac2be493e2 -r 4d83d27a955e Applications/StoneWebViewer/Version.cmake --- a/Applications/StoneWebViewer/Version.cmake Wed Nov 24 12:25:19 2021 +0100 +++ b/Applications/StoneWebViewer/Version.cmake Wed Nov 24 12:25:32 2021 +0100 @@ -23,6 +23,6 @@ set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") else() - set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.9.4") + set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.9.7") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") endif() diff -r 35ac2be493e2 -r 4d83d27a955e Applications/StoneWebViewer/WebApplication/app.css --- a/Applications/StoneWebViewer/WebApplication/app.css Wed Nov 24 12:25:19 2021 +0100 +++ b/Applications/StoneWebViewer/WebApplication/app.css Wed Nov 24 12:25:32 2021 +0100 @@ -2330,11 +2330,14 @@ line-height: 15px; width: 15px; height: 15px; - border-radius: 100%; + border-radius: 5px; background-color: gray; vertical-align: middle; text-align: center; - font-weight: bold; } + font-weight: bold; + width: max-content; + padding: 1px 5px; + line-height: 12px; } .wvSerieslist__information { font-size: 14px; diff -r 35ac2be493e2 -r 4d83d27a955e Applications/StoneWebViewer/WebApplication/app.js --- a/Applications/StoneWebViewer/WebApplication/app.js Wed Nov 24 12:25:19 2021 +0100 +++ b/Applications/StoneWebViewer/WebApplication/app.js Wed Nov 24 12:25:32 2021 +0100 @@ -150,7 +150,9 @@ videoUri: '', windowingCenter: 0, windowingWidth: 0, - instanceNumber: 0 + instanceNumber: 0, + contentDate: '', + contentTime: '', } }, watch: { @@ -171,7 +173,9 @@ this.windowingCenter = 0; this.windowingWidth = 0; this.instanceNumber = 0; - + this.contentDate = ''; + this.contentTime = '' + if (this.cineTimeoutId !== null) { clearTimeout(this.cineTimeoutId); this.cineTimeoutId = null; @@ -255,6 +259,8 @@ that.numberOfFrames = args.detail.numberOfFrames; that.quality = args.detail.quality; that.instanceNumber = args.detail.instanceNumber; + that.contentDate = args.detail.contentDate; + that.contentTime = args.detail.contentTime; } }); @@ -561,6 +567,9 @@ var studies = []; var posColor = 0; + // order series by SeriesNumber + sourceSeries.sort((a, b) => {return a[SERIES_NUMBER] - b[SERIES_NUMBER];}) + for (var i = 0; i < sourceStudies.length; i++) { var studyInstanceUid = sourceStudies[i][STUDY_INSTANCE_UID]; if (studyInstanceUid !== undefined) { @@ -938,6 +947,43 @@ } }, + FormatTime: function(time) + { + if (time === undefined || + time.length == 0) { + return ''; + } + else { + var format = this.globalConfiguration['TimeFormat']; + if (format === undefined) { + // No configuration for the date format, use the DICOM tag as such + return time; + } + else { + var timeRegexHMS = /([0-9]{2})([0-9]{2})([0-9]{2})/; + var timeRegexHMSms = /([0-9]{2})([0-9]{2})([0-9]{2}).([0-9]*)/ + var m = time.match(timeRegexHMSms); + if (m) { + format = format.replace(/hh/g, m[1]).replace(/mm/g, m[2]).replace(/ss/g, m[3]); + if (format.indexOf('f') != -1) { // format expects ms + return format.replace(/f/g, m[4]) + } else { + return format; + } + } + var m = time.match(timeRegexHMS); + if (m) { + format = format.replace(/hh/g, m[1]).replace(/mm/g, m[2]).replace(/ss/g, m[3]); + if (format.indexOf('f') != -1) { // format expects ms but we could not capture one + return format.replace(/.f/g, '') + } + } + + return time; + } + } + }, + DownloadJpeg: function() { var canvas = document.getElementById(this.GetActiveCanvas()); @@ -1097,6 +1143,10 @@ stone.SetDicomCacheSize(app.globalConfiguration.DicomCacheSize); } + if ('SkipSeriesFromModalities' in app.globalConfiguration) { + stone.SetSkipSeriesFromModalities(JSON.stringify(app.globalConfiguration.SkipSeriesFromModalities)); + } + // Bearer token is new in Stone Web viewer 2.0 var token = getParameterFromUrl('token'); if (token !== undefined) @@ -1161,7 +1211,7 @@ window.addEventListener('ResourcesLoaded', function() { - console.log('resources loaded'); + console.log('resources loaded: ', stone.GetStudiesCount(), 'studies &', stone.GetSeriesCount(), 'series'); var studies = []; for (var i = 0; i < stone.GetStudiesCount(); i++) { diff -r 35ac2be493e2 -r 4d83d27a955e Applications/StoneWebViewer/WebApplication/configuration.json --- a/Applications/StoneWebViewer/WebApplication/configuration.json Wed Nov 24 12:25:19 2021 +0100 +++ b/Applications/StoneWebViewer/WebApplication/configuration.json Wed Nov 24 12:25:32 2021 +0100 @@ -9,6 +9,15 @@ // "DateFormat" : "DD/MM/YYYY", /** + * Defines how times are displayed in the UI. If this option is not + * set, the DICOM tags will be displayed as such. "hh" will be + * replaced by the hour, "mm" by the minutes, "ss" by the seconds + * and ".f" by the fractions of seconds. + **/ + // "TimeFormat" : "hh:mm:ss.f", + + + /** * This option allows you to define windowing presets. * For each preset, you must provide a name, the window width * and window center. @@ -105,6 +114,11 @@ * image, this logo will be displayed at the bottom-left of the * Stone Web viewer. **/ - "InstitutionLogo" : "" + "InstitutionLogo" : "", + + /** + * Define a list of modality type that the viewer will ignore. + **/ + "SkipSeriesFromModalities": ["SR", "SEG", "PR"] } } diff -r 35ac2be493e2 -r 4d83d27a955e Applications/StoneWebViewer/WebApplication/index.html --- a/Applications/StoneWebViewer/WebApplication/index.html Wed Nov 24 12:25:19 2021 +0100 +++ b/Applications/StoneWebViewer/WebApplication/index.html Wed Nov 24 12:25:32 2021 +0100 @@ -243,7 +243,11 @@ />
{{ series[seriesIndex].numberOfFrames }}
+ v-if="series[seriesIndex].numberOfFrames != 0 || series[seriesIndex].tags[SERIES_NUMBER] !== undefined"> + #{{ series[seriesIndex].tags[SERIES_NUMBER] }} + - + {{ series[seriesIndex].numberOfFrames }} + @@ -672,7 +676,8 @@
{{ content.series.tags[STUDY_DESCRIPTION] }}
- {{ app.FormatDate(content.series.tags[STUDY_DATE]) }}
+ {{ app.FormatDate(contentDate) }} {{ app.FormatTime(contentTime) }}
+ {{ app.FormatDate(content.series.tags[STUDY_DATE]) }}
{{ content.series.tags[SERIES_NUMBER] }} | {{ content.series.tags[SERIES_DESCRIPTION] }}
@@ -813,9 +818,6 @@
- - - diff -r 35ac2be493e2 -r 4d83d27a955e Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp --- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Wed Nov 24 12:25:19 2021 +0100 +++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Wed Nov 24 12:25:32 2021 +0100 @@ -98,7 +98,7 @@ #include #include #include - +#include #if !defined(STONE_WEB_VIEWER_EXPORT) // We are not running ParseWebAssemblyExports.py, but we're compiling the wasm @@ -321,6 +321,7 @@ boost::shared_ptr metadataLoader_; std::set scheduledVirtualSeriesThumbnails_; VirtualSeries virtualSeries_; + std::vector skipSeriesFromModalities_; explicit ResourcesLoader(OrthancStone::ILoadersContext& context, const OrthancStone::DicomSource& source) : @@ -342,20 +343,41 @@ LOG(INFO) << "resources loaded: " << dicom.GetSize() << ", " << Orthanc::EnumerationToString(payload.GetValue()); + std::vector seriesIdsToRemove; + if (payload.GetValue() == Orthanc::ResourceType_Series) { + // the 'dicom' var is actually equivalent to the 'series_' member in this case + for (size_t i = 0; i < dicom.GetSize(); i++) { - std::string studyInstanceUid, seriesInstanceUid; + std::string studyInstanceUid, seriesInstanceUid, modality; if (dicom.GetResource(i).LookupStringValue( studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) && dicom.GetResource(i).LookupStringValue( - seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false)) + seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) && + dicom.GetResource(i).LookupStringValue( + modality, Orthanc::DICOM_TAG_MODALITY, false)) { - thumbnailsLoader_->ScheduleLoadThumbnail(source_, "", studyInstanceUid, seriesInstanceUid); - metadataLoader_->ScheduleLoadSeries(PRIORITY_LOW + 1, source_, studyInstanceUid, seriesInstanceUid); + // skip series that should not be displayed + if (std::find(skipSeriesFromModalities_.begin(), skipSeriesFromModalities_.end(), modality) == skipSeriesFromModalities_.end()) + { + thumbnailsLoader_->ScheduleLoadThumbnail(source_, "", studyInstanceUid, seriesInstanceUid); + metadataLoader_->ScheduleLoadSeries(PRIORITY_LOW + 1, source_, studyInstanceUid, seriesInstanceUid); + } + + else + { + seriesIdsToRemove.push_back(seriesInstanceUid); + } } } + + for (size_t i = 0; i < seriesIdsToRemove.size(); i++) + { + LOG(INFO) << "series to hide: " << seriesIdsToRemove[i]; + dicom.RemoveResource(seriesIdsToRemove[i]); + } } if (pending_ == 0) @@ -515,6 +537,11 @@ } public: + void SetSkipSeriesFromModalities(const std::vector& skipSeriesFromModalities) + { + skipSeriesFromModalities_ = skipSeriesFromModalities; + } + static boost::shared_ptr Create(OrthancStone::ILoadersContext::ILock& lock, const OrthancStone::DicomSource& source) { @@ -1450,7 +1477,9 @@ size_t currentFrame, size_t countFrames, DisplayedFrameQuality quality, - unsigned int instanceNumber) = 0; + unsigned int instanceNumber, + const std::string& contentDate, + const std::string& contentTime) = 0; // "click" is a 3D vector in world coordinates virtual void SignalCrosshair(const ViewerViewport& viewport, @@ -1972,13 +2001,15 @@ { const Orthanc::DicomMap& instance = frames_->GetInstanceOfFrame(cursor_->GetCurrentIndex()).GetTags(); - uint32_t instanceNumber; - if (!instance.ParseUnsignedInteger32(instanceNumber, Orthanc::DICOM_TAG_INSTANCE_NUMBER)) - { - instanceNumber = 0; - } - - observer_->SignalFrameUpdated(*this, cursorIndex, frames_->GetFramesCount(), quality, instanceNumber); + uint32_t instanceNumber = 0; + std::string contentDate; + std::string contentTime; + + instance.ParseUnsignedInteger32(instanceNumber, Orthanc::DICOM_TAG_INSTANCE_NUMBER); + instance.LookupStringValue(contentDate, Orthanc::DicomTag(0x0008, 0x0023), false); + instance.LookupStringValue(contentTime, Orthanc::DicomTag(0x0008, 0x0033), false); + + observer_->SignalFrameUpdated(*this, cursorIndex, frames_->GetFramesCount(), quality, instanceNumber, contentDate, contentTime); } } @@ -2575,7 +2606,7 @@ if (observer_.get() != NULL) { observer_->SignalFrameUpdated(*this, cursor_->GetCurrentIndex(), - frames_->GetFramesCount(), DisplayedFrameQuality_None, 0); + frames_->GetFramesCount(), DisplayedFrameQuality_None, 0, "", ""); } centralPhysicalWidth_ = 1; @@ -3257,7 +3288,9 @@ size_t currentFrame, size_t countFrames, DisplayedFrameQuality quality, - unsigned int instanceNumber) ORTHANC_OVERRIDE + unsigned int instanceNumber, + const std::string& contentDate, + const std::string& contentTime) ORTHANC_OVERRIDE { EM_ASM({ const customEvent = document.createEvent("CustomEvent"); @@ -3266,13 +3299,19 @@ "currentFrame" : $1, "numberOfFrames" : $2, "quality" : $3, - "instanceNumber" : $4 }); + "instanceNumber" : $4, + "contentDate" : UTF8ToString($5), + "contentTime" : UTF8ToString($6), + }); window.dispatchEvent(customEvent); }, viewport.GetCanvasId().c_str(), static_cast(currentFrame), static_cast(countFrames), - quality, instanceNumber); + quality, + instanceNumber, + contentDate.c_str(), + contentTime.c_str()); UpdateReferenceLines(); } @@ -3560,6 +3599,27 @@ EMSCRIPTEN_KEEPALIVE + void SetSkipSeriesFromModalities(const char* value) + { + try + { + LOG(WARNING) << "SetSkipSeriesFromModalities " << value; + + Json::Value modalities; + Orthanc::Toolbox::ReadJson(modalities, value); + std::vector skipSeriesFromModalities; + + for (Json::Value::ArrayIndex i = 0; i < modalities.size(); i++) + { + skipSeriesFromModalities.push_back(modalities[i].asString()); + } + GetResourcesLoader().SetSkipSeriesFromModalities(skipSeriesFromModalities); + } + EXTERN_CATCH_EXCEPTIONS; + } + + + EMSCRIPTEN_KEEPALIVE void FetchAllStudies() { try diff -r 35ac2be493e2 -r 4d83d27a955e OrthancStone/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake --- a/OrthancStone/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake Wed Nov 24 12:25:19 2021 +0100 +++ b/OrthancStone/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake Wed Nov 24 12:25:32 2021 +0100 @@ -132,6 +132,12 @@ set(ORTHANC_FRAMEWORK_MD5 "9b86e6f00e03278293cd15643cc0233f") elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.4") set(ORTHANC_FRAMEWORK_MD5 "6d5ca4a73ac7d42445041ca79de1624d") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.5") + set(ORTHANC_FRAMEWORK_MD5 "10fc64de1254a095e5d3ed3931f0cfbb") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.6") + set(ORTHANC_FRAMEWORK_MD5 "4b5d05683d747c29b2860ad79d11e62e") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.7") + set(ORTHANC_FRAMEWORK_MD5 "c912bbb860d640d3ae3003b5c9698205") # Below this point are development snapshots that were used to # release some plugin, before an official release of the Orthanc diff -r 35ac2be493e2 -r 4d83d27a955e OrthancStone/Sources/Loaders/LoadedDicomResources.h --- a/OrthancStone/Sources/Loaders/LoadedDicomResources.h Wed Nov 24 12:25:19 2021 +0100 +++ b/OrthancStone/Sources/Loaders/LoadedDicomResources.h Wed Nov 24 12:25:32 2021 +0100 @@ -109,6 +109,17 @@ return resources_.find(id) != resources_.end(); } + void RemoveResource(const std::string& id) + { + if (HasResource(id)) + { + Resource* resource = resources_[id]; + delete resource; + resources_.erase(id); + flattened_.clear(); // Invalidate the flattened version + } + } + void MergeResource(Orthanc::DicomMap& target, const std::string& id) const; diff -r 35ac2be493e2 -r 4d83d27a955e OrthancStone/Sources/Messages/IObservable.cpp --- a/OrthancStone/Sources/Messages/IObservable.cpp Wed Nov 24 12:25:19 2021 +0100 +++ b/OrthancStone/Sources/Messages/IObservable.cpp Wed Nov 24 12:25:32 2021 +0100 @@ -27,6 +27,7 @@ #include #include +#include namespace OrthancStone { @@ -88,6 +89,10 @@ { LOG(ERROR) << "Exception on callable: " << e.What(); } + catch (std::exception& e) + { + LOG(ERROR) << "C++ exception on callable: " << e.what(); + } catch (...) { LOG(ERROR) << "Native exception on callable"; diff -r 35ac2be493e2 -r 4d83d27a955e TODO --- a/TODO Wed Nov 24 12:25:19 2021 +0100 +++ b/TODO Wed Nov 24 12:25:32 2021 +0100 @@ -41,6 +41,9 @@ * Vertical "timeline" to see the position of the current frame in the series, and to change the current frame by clicking on the timeline. +* Display a pixel probe with the Hounsfield Unit. + https://groups.google.com/g/orthanc-users/c/m7S0wbYYW5s/m/MBaxIQ_IAAAJ + * Display video files even if the Orthanc REST API is not available (using pure DICOMweb). This could possible be done using the DICOMweb Bulk Data URI, and/or a dedicated JavaScript video player.