Mercurial > hg > orthanc-stone
changeset 1815:b81775f1b196
New tools for annotations: segment length, circle, angle and eraser
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 25 May 2021 18:07:52 +0200 |
parents | 53f3411bf94b |
children | dccdc7e59929 |
files | Applications/StoneWebViewer/NEWS Applications/StoneWebViewer/WebApplication/app.js Applications/StoneWebViewer/WebApplication/index.html Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp |
diffstat | 4 files changed, 154 insertions(+), 11 deletions(-) [+] |
line wrap: on
line diff
--- a/Applications/StoneWebViewer/NEWS Tue May 25 15:52:38 2021 +0200 +++ b/Applications/StoneWebViewer/NEWS Tue May 25 18:07:52 2021 +0200 @@ -1,6 +1,7 @@ Pending changes in the mainline =============================== +* New tools for annotations: segment length, circle, angle and eraser * Patient birth date is now displayed in the overlays * New argument "token" to set HTTP header "Authorization: Bearer <token>" for each request to the DICOMweb server
--- a/Applications/StoneWebViewer/WebApplication/app.js Tue May 25 15:52:38 2021 +0200 +++ b/Applications/StoneWebViewer/WebApplication/app.js Tue May 25 18:07:52 2021 +0200 @@ -391,6 +391,8 @@ globalConfiguration: {}, creatingArchive: false, archiveJob: '', + annotationsCurrentAction: stone.WebViewerAction.NONE, // dummy value + annotationsBackupAction: stone.WebViewerAction.NONE, // dummy value modalWarning: false, modalNotDiagnostic: false, @@ -652,6 +654,8 @@ sopInstanceUid: info.sopInstanceUid }; } + + this.ResetAnnotationsAction(); }, ClickSeries: function(seriesIndex) { @@ -749,6 +753,7 @@ } this.FitContent(); + this.ResetAnnotationsAction(); }, UpdateSeriesThumbnail: function(seriesInstanceUid) { @@ -860,9 +865,37 @@ SetMouseButtonActions: function(left, middle, right) { this.mouseActionsVisible = false; + this.annotationsBackupAction = stone.WebViewerAction.NONE; stone.SetMouseButtonActions(left, middle, right); }, + SetAnnotationsAction: function(action) { + if (this.annotationsCurrentAction == action) { + this.ResetAnnotationsAction(); + } else { + this.annotationsCurrentAction = action; + + if (this.annotationsBackupAction == stone.WebViewerAction.NONE) { + this.annotationsBackupAction = stone.GetLeftMouseButtonAction(); + } + + stone.SetMouseButtonActions(action, + stone.GetMiddleMouseButtonAction(), + stone.GetRightMouseButtonAction()); + } + }, + + ResetAnnotationsAction: function() { + if (this.annotationsBackupAction != stone.WebViewerAction.NONE) { + stone.SetMouseButtonActions(this.annotationsBackupAction, + stone.GetMiddleMouseButtonAction(), + stone.GetRightMouseButtonAction()); + } + + this.annotationsBackupAction = stone.WebViewerAction.NONE; + this.annotationsCurrentAction = stone.WebViewerAction.NONE; + }, + LoadOsiriXAnnotations: function(xml, clearPrevious) { if (stone.LoadOsiriXAnnotations(xml, clearPrevious)) { @@ -1037,6 +1070,14 @@ var seriesInstanceUid = args.detail.seriesInstanceUid; that.UpdateIsSeriesComplete(studyInstanceUid, seriesInstanceUid); }); + + window.addEventListener('StoneAnnotationAdded', function() { + that.ResetAnnotationsAction(); + }); + + window.addEventListener('StoneAnnotationRemoved', function() { + that.ResetAnnotationsAction(); + }); } }); @@ -1144,7 +1185,6 @@ - function ParseJsonWithComments(json) { if (typeof(json) == 'string') {
--- a/Applications/StoneWebViewer/WebApplication/index.html Tue May 25 15:52:38 2021 +0200 +++ b/Applications/StoneWebViewer/WebApplication/index.html Tue May 25 18:07:52 2021 +0200 @@ -472,6 +472,8 @@ <div class="ng-scope inline-object"> <button class="wvButton--underline text-center" + v-bind:class="{ active: (annotationsCurrentAction == stone.WebViewerAction.CREATE_SEGMENT) }" + v-on:click="SetAnnotationsAction(stone.WebViewerAction.CREATE_SEGMENT)" data-toggle="tooltip" data-title="Measure length"> <i class="fas fa-arrows-alt-h"></i> </button> @@ -479,6 +481,8 @@ <div class="ng-scope inline-object"> <button class="wvButton--underline text-center" + v-bind:class="{ active: (annotationsCurrentAction == stone.WebViewerAction.CREATE_ANGLE) }" + v-on:click="SetAnnotationsAction(stone.WebViewerAction.CREATE_ANGLE)" data-toggle="tooltip" data-title="Measure angle"> <i class="fas fa-angle-left fa-lg"></i> </button> @@ -486,6 +490,8 @@ <div class="ng-scope inline-object"> <button class="wvButton--underline text-center" + v-bind:class="{ active: (annotationsCurrentAction == stone.WebViewerAction.CREATE_CIRCLE) }" + v-on:click="SetAnnotationsAction(stone.WebViewerAction.CREATE_CIRCLE)" data-toggle="tooltip" data-title="Measure circle"> <i class="far fa-circle"></i> </button> @@ -493,6 +499,15 @@ <div class="ng-scope inline-object"> <button class="wvButton--underline text-center" + v-bind:class="{ active: (annotationsCurrentAction == stone.WebViewerAction.REMOVE_MEASURE) }" + v-on:click="SetAnnotationsAction(stone.WebViewerAction.REMOVE_MEASURE)" + data-toggle="tooltip" data-title="Delete measurement"> + <i class="fas fa-trash"></i> + </button> + </div> + + <div class="ng-scope inline-object"> + <button class="wvButton--underline text-center" data-toggle="tooltip" data-title="Synchronized browsing" v-bind:class="{ 'active' : synchronizedBrowsing }" v-on:click="synchronizedBrowsing = !synchronizedBrowsing">
--- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Tue May 25 15:52:38 2021 +0200 +++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Tue May 25 18:07:52 2021 +0200 @@ -130,7 +130,9 @@ enum STONE_WEB_VIEWER_EXPORT WebViewerAction { - WebViewerAction_Windowing, + WebViewerAction_None, + + WebViewerAction_Windowing, WebViewerAction_Zoom, WebViewerAction_Pan, WebViewerAction_Rotate, @@ -139,7 +141,7 @@ WebViewerAction_CreateAngle, WebViewerAction_CreateCircle, WebViewerAction_CreateSegment, - WebViewerAction_DeleteMeasure + WebViewerAction_RemoveMeasure }; @@ -160,11 +162,12 @@ case WebViewerAction_Rotate: return OrthancStone::MouseAction_Rotate; + case WebViewerAction_None: case WebViewerAction_Crosshair: case WebViewerAction_CreateAngle: case WebViewerAction_CreateCircle: case WebViewerAction_CreateSegment: - case WebViewerAction_DeleteMeasure: + case WebViewerAction_RemoveMeasure: return OrthancStone::MouseAction_None; default: @@ -1323,6 +1326,10 @@ virtual void SignalStoneAnnotationsChanged(const ViewerViewport& viewport, const std::string& sopInstanceUid, size_t frame) = 0; + + virtual void SignalStoneAnnotationAdded(const ViewerViewport& viewport) = 0; + + virtual void SignalStoneAnnotationRemoved(const ViewerViewport& viewport) = 0; }; private: @@ -2287,11 +2294,21 @@ void Handle(const OrthancStone::AnnotationsSceneLayer::AnnotationAddedMessage& message) { RefreshAnnotations(true /* save */); + + if (observer_.get() != NULL) + { + observer_->SignalStoneAnnotationAdded(*this); + } } void Handle(const OrthancStone::AnnotationsSceneLayer::AnnotationRemovedMessage& message) { RefreshAnnotations(true /* save */); + + if (observer_.get() != NULL) + { + observer_->SignalStoneAnnotationRemoved(*this); + } } public: @@ -2771,15 +2788,42 @@ } else { + // Only the left mouse button can be used to edit/create/remove annotations + if (event.GetMouseButton() == OrthancStone::MouseButton_Left) { - std::unique_ptr<OrthancStone::IViewport::ILock> lock2(lock1->Lock()); - - std::unique_ptr<OrthancStone::IFlexiblePointerTracker> t; - t.reset(viewer_.stoneAnnotations_->CreateTracker(event.GetMainPosition(), lock2->GetController().GetScene())); - - if (t.get() != NULL) + switch (leftAction_) { - return t.release(); + case WebViewerAction_CreateAngle: + viewer_.stoneAnnotations_->SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_Angle); + break; + + case WebViewerAction_CreateCircle: + viewer_.stoneAnnotations_->SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_Circle); + break; + + case WebViewerAction_CreateSegment: + viewer_.stoneAnnotations_->SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_Segment); + break; + + case WebViewerAction_RemoveMeasure: + viewer_.stoneAnnotations_->SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_Remove); + break; + + default: + viewer_.stoneAnnotations_->SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_Edit); + break; + } + + { + std::unique_ptr<OrthancStone::IViewport::ILock> lock2(lock1->Lock()); + + std::unique_ptr<OrthancStone::IFlexiblePointerTracker> t; + t.reset(viewer_.stoneAnnotations_->CreateTracker(event.GetMainPosition(), lock2->GetController().GetScene())); + + if (t.get() != NULL) + { + return t.release(); + } } } @@ -3166,6 +3210,28 @@ } } } + + virtual void SignalStoneAnnotationAdded(const ViewerViewport& viewport) ORTHANC_OVERRIDE + { + EM_ASM({ + const customEvent = document.createEvent("CustomEvent"); + customEvent.initCustomEvent("StoneAnnotationAdded", false, false, + { "canvasId" : UTF8ToString($0) }); + window.dispatchEvent(customEvent); + }, + viewport.GetCanvasId().c_str()); + } + + virtual void SignalStoneAnnotationRemoved(const ViewerViewport& viewport) ORTHANC_OVERRIDE + { + EM_ASM({ + const customEvent = document.createEvent("CustomEvent"); + customEvent.initCustomEvent("StoneAnnotationRemoved", false, false, + { "canvasId" : UTF8ToString($0) }); + window.dispatchEvent(customEvent); + }, + viewport.GetCanvasId().c_str()); + } }; @@ -3699,6 +3765,27 @@ EMSCRIPTEN_KEEPALIVE + int GetLeftMouseButtonAction() + { + return static_cast<int>(leftButtonAction_); + } + + + EMSCRIPTEN_KEEPALIVE + int GetMiddleMouseButtonAction() + { + return static_cast<int>(middleButtonAction_); + } + + + EMSCRIPTEN_KEEPALIVE + int GetRightMouseButtonAction() + { + return static_cast<int>(rightButtonAction_); + } + + + EMSCRIPTEN_KEEPALIVE void FitForPrint() { try