# HG changeset patch # User Alain Mazy # Date 1632150503 -7200 # Node ID 58681a5c727ba94ad368960582dfee00fb6e5421 # Parent be88206f8d786b8a57b360858e6fffc3597254c1 overlay: display ContentDate/ContentTime instead of StudyDate if available + new 'TimeFormat' option diff -r be88206f8d78 -r 58681a5c727b Applications/StoneWebViewer/BuildInstructions.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/BuildInstructions.txt Mon Sep 20 17:08:23 2021 +0200 @@ -0,0 +1,68 @@ +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 +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 \ No newline at end of file diff -r be88206f8d78 -r 58681a5c727b Applications/StoneWebViewer/NEWS --- a/Applications/StoneWebViewer/NEWS Mon Sep 06 22:17:32 2021 +0200 +++ b/Applications/StoneWebViewer/NEWS Mon Sep 20 17:08:23 2021 +0200 @@ -1,6 +1,12 @@ Pending changes in the mainline =============================== +* 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. + Version 2.2 (2021-08-31) ======================== diff -r be88206f8d78 -r 58681a5c727b Applications/StoneWebViewer/WebApplication/app.js --- a/Applications/StoneWebViewer/WebApplication/app.js Mon Sep 06 22:17:32 2021 +0200 +++ b/Applications/StoneWebViewer/WebApplication/app.js Mon Sep 20 17:08:23 2021 +0200 @@ -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; } }); @@ -938,6 +944,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()); diff -r be88206f8d78 -r 58681a5c727b Applications/StoneWebViewer/WebApplication/configuration.json --- a/Applications/StoneWebViewer/WebApplication/configuration.json Mon Sep 06 22:17:32 2021 +0200 +++ b/Applications/StoneWebViewer/WebApplication/configuration.json Mon Sep 20 17:08:23 2021 +0200 @@ -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. diff -r be88206f8d78 -r 58681a5c727b Applications/StoneWebViewer/WebApplication/index.html --- a/Applications/StoneWebViewer/WebApplication/index.html Mon Sep 06 22:17:32 2021 +0200 +++ b/Applications/StoneWebViewer/WebApplication/index.html Mon Sep 20 17:08:23 2021 +0200 @@ -672,7 +672,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 +814,6 @@
- - - diff -r be88206f8d78 -r 58681a5c727b Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp --- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Mon Sep 06 22:17:32 2021 +0200 +++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Mon Sep 20 17:08:23 2021 +0200 @@ -1450,7 +1450,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 +1974,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 +2579,7 @@ if (observer_.get() != NULL) { observer_->SignalFrameUpdated(*this, cursor_->GetCurrentIndex(), - frames_->GetFramesCount(), DisplayedFrameQuality_None, 0); + frames_->GetFramesCount(), DisplayedFrameQuality_None, 0, "", ""); } centralPhysicalWidth_ = 1; @@ -3257,7 +3261,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 +3272,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(); }