Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add referenceCursors tool #275

Merged
merged 30 commits into from
Nov 23, 2022
Merged

feat: add referenceCursors tool #275

merged 30 commits into from
Nov 23, 2022

Conversation

doepnern
Copy link
Contributor

@doepnern doepnern commented Nov 1, 2022

Hey, I really love this repository and especially the improved API in the new version.
I created this tool for my personal use, its purpose is mostly to act like a linking of camera position and showing a reference to what your cursor is currently hovering over. It also helps a lot when you have multiple series which are not in the same Orientation, to have a feel for what you are looking at in different orientations. This is somewhat similar to the crosshair that exists, but it also works with Stack viewports, which I use it mostly for.
Would there be an interest in adding this tool, I would take the time to write tests as well if that is the case.
Thank you for your great library anyway!
Niclas

p.s. here is a small video
https://user-images.githubusercontent.com/32524613/199220760-a20ea8e2-9efb-4335-a763-0325ecba5f43.mp4

…orSync is displayed regardless of distance, create configurable distance and also sync the position of all viewports over which the mouse is not to scroll to a slice that is close to the currentMousePosition in 3d space
…on imageChange events, added configuration for max display distance
@netlify
Copy link

netlify bot commented Nov 1, 2022

Deploy Preview for cornerstone-3d-docs ready!

Name Link
🔨 Latest commit 5b2da38
🔍 Latest deploy log https://app.netlify.com/sites/cornerstone-3d-docs/deploys/637e187d7a021a000816e987
😎 Deploy Preview https://deploy-preview-275--cornerstone-3d-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@sedghi
Copy link
Member

sedghi commented Nov 4, 2022

@doepnern Hey
Thanks for your contribution. First of all, this is really impressive PR.
Yes, we have seen this tool on other viewers and would love to also include it in Cornerstone3D and eventually on OHIF.

I will go ahead and review this now

@doepnern
Copy link
Contributor Author

doepnern commented Nov 4, 2022

I am not completely sure if I'm happy with how this works right now. Maybe it would be easier to not use the annotation tool at all as this doesnt use any of the features where having the worldPosition of the annotation is useful and the easiest way to render the crosshair would be to just have the current enabled element where the mouse is over and the canvas position and then just calculate the world position for every viewport on render.

@doepnern
Copy link
Contributor Author

doepnern commented Nov 4, 2022

And in the current version there would need to be a cleanup of the listeners with a setMouseOverElement(null), I am not quite sure where to put it as onSetToolDisabled ist not always called (for example not if the toolGroup is destroyed

Copy link
Member

@sedghi sedghi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I did my first pass to understand what is going on and basically put my questions also, I have other comments as well, but I prefer to get your opinions/answers for my questions before giving you the full review.

PS: I first thought, we can avoid adding an annotation to the state, since basically it does not serve as any purpose, but then our rendering loop handles each element separately so I think right now it is fine, if we just addAnnotation

Again, great job, and welcome

packages/tools/src/tools/CursorCrosshairSync.ts Outdated Show resolved Hide resolved
@doepnern
Copy link
Contributor Author

doepnern commented Nov 6, 2022

Okay, so I hopefully applied most of the changes you suggested. Thanks a lot for the extensive feedback.

The reason I was working with imageChange events:
Currently on every mouseMove the annotations world position is changed which works fine. However if the viewport over which the cursor is currently positioned is for example scrolled via StackScrollMouseWheelTool, then no mouseMove event is triggered but the world position of the annotation should change. If all viewports would be rerendered we could just calculate it based on the canvasPosition but only the Viewport being scrolled gets rerended.

This leads to the cursor being stale until the next mouseMove is triggered. Also this will prevent the stacks to sync during scrolling for the same reason.

The only way I found around adding the event listeners now is:
When a Stack change happens, it will cause the annotation on the viewport which was scrolled to be rendered

  • check during render of annotation if canvasPosition still corresponds to saved WorldPosition (this wont be true if a StackChange happened)
  • if stackChange happened, update worldPosition and trigger rerender for all viewports

There after some longer debugging I noticed that calling triggerAnnotationRerender during an annotationRender will cause the whole tool to break (even when excluding the currently rendering viewport). For that reason, I had to wrap the call into a setTimeout. Feel kind of "hacky" but I just don't see a completely easy way to do this.

You can test the whole problem by commenting out the block in the renderAnnotation checking for the stackChange. When using StackScrollWheel the cursor won't be synced and the stack states won't be synced until a mouseMove is triggered.

About the cursor being rendered in all viewports:
I added it to the viewport with the cursor as well now.
I'm not sure what to do with the mouse though. Removing it completely might be confusing as all cursors will look the same then. Maybe the main cursor should have a different design to not confuse the user. (I thought about a different color, but then how do you know which one is the main on 2 viewports and how would you handle custom color choices). I'm very open to suggestions.

@doepnern
Copy link
Contributor Author

doepnern commented Nov 8, 2022

Thanks for all the suggestions. onCameraModified is actually a perfect way. It would be really helpful if you would have a small overview of functions a tool can implement at some point. What I noticed is, that when you set the tool to passive there are errors in the console when clicking on the screen for functions not being implemented even though I'm now extending the AnnotationDisplayTool. You can try it out by setting the tool to passive in the example

},
};

const annotationId = this._addAnnotation(element, annotation);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check which is basically checking annotation manager if there is one exists can be done very first thing at the top of this method and avoid all the object and logics up here

@sedghi sedghi marked this pull request as ready for review November 8, 2022 23:43
@sedghi
Copy link
Member

sedghi commented Nov 8, 2022

Thanks for the updates, this is good to go except I tried it and for the active viewport (the one with mouse) I see two cursors for some reason. Does this happen on your machine too?

Update: I think I know what is going on, the active tool is set to be Pan, so there is Pan SVG Cursor, but also we decided to show the cursor for the active viewport too, so I guess you need to hide the cursor when the tool is set to active as a call back setToolActive, see other annotation tools for how to hide cursor

Update2: Basically your tool and some other tools only should have two states: Active and Disabled. Enabled, and Passive does not have meaning here. Previously we had mixins for this, that redirected setToolPassive to setToolDisabled (https://github.com/cornerstonejs/cornerstoneTools/blob/master/src/mixins/activeOrDisabledBinaryTool.js) let me see what I can do

8a3d4587-7a93-4b6d-a04b-8a57d10330cb.webm

@sedghi
Copy link
Member

sedghi commented Nov 9, 2022

Another comment after thinking, your tool has active state, meaning that one should activate it, so I expect the demo makes setToolActive which changes the cursor (right now it shows pan)

@doepnern
Copy link
Contributor Author

I actually like the option of still having the cursor of another tool. In actual usage this tool would mostly be used while still having a tool active, so I dont like hiding the primary tool that much. I changed the look of the tool in the element with the cursor to hopefully not conflict with another tool and added a configuration to disable the other tools cursor. I was having a lot of issues with curors being replaced depending on the order in which tools are enabled. Would it maybe be useful to only change the cursor to the default one if there is no active primary cursor(I added a check for that in the ToolGroup). Feel free to remove that if you dont feel like this is wanted.

@doepnern doepnern requested a review from sedghi November 17, 2022 13:09
preventHandleOutsideImage: false,
displayThreshold: 5,
positionSync: true,
disableCursor: false,
Copy link
Member

@sedghi sedghi Nov 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default for disableCursor to be false hmmm, going back to the discussion on the cursors for this tool, I don't understand why one needs to have the pan cursor on and also the cursor for this tool at the same time. It is kind of not in harmony with the rest of the library in which the active tool on the mouse primary claims the cursor and not other tools. Can we set this tool to behave the same way by default? that it overwrite its cursor by default?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignore this comment

}
) {
super(toolProps, defaultToolProps);
this._disableCursorEnabled = this.configuration.disableCursor;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might cause a bug when configuration changes, since you are only assigning it on the construtor, maybe always use this.configuration.disableCursor?

const isInBounds = true;
if (isInBounds) {
viewport.setCamera({ focalPoint: newFocalPoint });
const renderingeEngine = viewport.getRenderingEngine();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

Copy link
Member

@sedghi sedghi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost there, see my comments

  1. Is there any usecase for having positionSync off? I feel like user wants it always on, and if it is off, it will cause confusion when other viewports not showing the same image

  2. The demo doesn't work when switching between disable cursor on and off (probably related to my comment for the constructor)

  3. There is a one pixel offset between the pan cursor and your tool cursor, do you think that is easy to fix?

@doepnern
Copy link
Contributor Author

doepnern commented Nov 23, 2022

@sedghi
Good points,

  1. I think mostly the sync is wanted, however it might interfere in some cases with some other tools or syncing mechanisms. Also the cursor is only displayed in the other viewport if the closest distance is smaller than displayThreshold, so there will be no cursor displayed if it doesn't make sense to display it.
  2. This is a more complicated point and I decided for this implementation because the logic of hiding the cursor is pretty impractical to work with, namely:
    https://github.com/cornerstonejs/cornerstone3D-beta/blob/a923b6888e80838edc2253fb6a82f5827b90724e/packages/tools/src/cursors/elementCursor.ts#L17-L37
    Problems I had:
  • Calling hideElementCursor twice without a resetElementCursor will set cursors[0] and cursors [1] to "none" -> This will lead to resetElementCursor to never work again (as it just sets the cursor to cursor[1]) until some other tool calls setElementCursor

This will also be a problem if 2 tools want to call hideElementCursor

So one would need to be sure to always clean up hideElemetCursor by calling resetElementCursor. As there is no onToolConfigurationChange Event the easiest way I found to do that was to set the internal _disableCursorEnabled every time the tool is activated and call the cleanup when the tool is disabled depending on that state. If you want to change the setting you have to deactivate and activate the tool once.

  1. No idea on this one, I think it might have something to do with how the svgCursors are drawn in differentce to the Annotations

@sedghi
Copy link
Member

sedghi commented Nov 23, 2022

Yeah the cursor API is probably the worst API in the whole repo, and I tried to fix it couple of times but initial implementation does not makes sense at all (and not my fault :D)

I think this is good to go

Thanks again! and looking forward to your next contribution!

Copy link
Member

@sedghi sedghi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work

@sedghi sedghi merged commit 3303246 into cornerstonejs:main Nov 23, 2022
sedghi added a commit that referenced this pull request Dec 7, 2022
* fix: mouse-up should not unhighlight annotations (#305)

* fix: annotation highlighted and tooling for ellipticalROI

* update build

* fix tests

* chore(release): publish [skip ci]

 - docs@0.7.8
 - @cornerstonejs/tools@0.29.7

* fix: stack viewport flip scroll (#304)

* fix: use focal point for pan cache for stack viewport

* fix: pan dir with flip

* fix pan values while flipped

* update build

* apply review comments

* fix build

* chore(release): publish [skip ci]

 - @cornerstonejs/core@0.21.5
 - docs@0.7.9
 - @cornerstonejs/streaming-image-volume-loader@0.6.6
 - @cornerstonejs/tools@0.29.8

* feat: add referenceCursors tool (#275)

* added basic cursorCrosshairSync tool with example, TODO: for now cursorSync is displayed regardless of distance, create configurable distance and also sync the position of all viewports over which the mouse is not to scroll to a slice that is close to the currentMousePosition in 3d space

* addde stack syncing for StackViewport and syncing for volumeViewport on imageChange events, added configuration for max display distance

* refactored tool functions

* added comment to possible bug

* added configuration options to example

* changed look of crosshair to 4 lines with central space

* undid local tsconfig change

* undid yarn.lock changes

* added tool to example-info.json

* removed from example-runner because it broke build

* readded example and fixed typo

* readded example-info and changed example to trigger rebuild

* added cleanup for mouseoverElement when tool is disabled

* added cleanup when tool gets disabled, this does not get called when toolGroup gets destroyed, might cause remaining listeners

* applied naming changes, reworked adding annotation logic

* removed event listeners and moved logic to check for stack scrolling into rendering logic

* added planeDistanceToPoint to planar utilities

* added getClosestStackImageIndexForPoint

* rewrote logic to use onCameraModified

* updated example-info

* fixed bug with 0 being falsey

* added logic to remove cursor if wanted

* modified toolGroup so that setting a tool active only changes the cursor to default if there is no primary mouse cursor

* fixed bug not updating disable cursor

* fixed missing parentheses from merge

* readded scrollWheel scrolling and api changes

* fixed typos

* chore(release): publish [skip ci]

 - @cornerstonejs/core@0.22.0
 - docs@0.7.10
 - @cornerstonejs/streaming-image-volume-loader@0.6.7
 - @cornerstonejs/tools@0.30.0

* fix: ZoomTool fix for polyData actors with no imageData (#308)

* chore(release): publish [skip ci]

 - docs@0.7.11
 - @cornerstonejs/tools@0.30.1

* fix: If planar annotation is not visible, filter it (#318)

Co-authored-by: edward65 <edward@afxmedical.com>

* fix: filter planarFreeHandeROI based on parallel normals instead of equal normals. (#315)

Co-authored-by: Ramon Emiliani <ramon@afxmedical.com>

* fix: get correct imageData with targetId in BaseTool (#294)

* limit disabled element not need to render

* Update BaseTool.ts

fix: get correct viewport when there are multiple viewport with same stack data

Co-authored-by: chendingmiao <cdm@tomtaw.com.cn>

* chore(release): publish [skip ci]

 - docs@0.7.12
 - @cornerstonejs/tools@0.30.2

* fix: htj2k and keymodifier (#313)

* fix(htj2k):Support htj2k in the streaming volume loader

* fix(decodeImage):Fix htj2k image decode and mouse key modifiers

* Update for PR

* update ci build

* chore(release): publish [skip ci]

 - docs@0.7.13
 - @cornerstonejs/streaming-image-volume-loader@0.6.8
 - @cornerstonejs/tools@0.30.3

* fix: coronal view should not be flipped (#321)

* chore(release): publish [skip ci]

 - @cornerstonejs/core@0.22.1
 - docs@0.7.14
 - @cornerstonejs/streaming-image-volume-loader@0.6.9
 - @cornerstonejs/tools@0.30.4

* fix: bidirectional tool when short and long axis changes (#309)

* fix rotation for handles

* fix: short axis movement

* fix: bidirectional tool incorrect interaction

* chore(release): publish [skip ci]

 - @cornerstonejs/core@0.22.2
 - docs@0.7.15
 - @cornerstonejs/streaming-image-volume-loader@0.6.10
 - @cornerstonejs/tools@0.30.5

* fix(volumeViewport): Add optional scaling as the volume can be undefined (#323)

While trying to get the volume from the cache, it can be undefined so getting the scaling attribute would throw an error in that case.
This is a quick fix

* chore(release): publish [skip ci]

 - @cornerstonejs/core@0.22.3
 - docs@0.7.16
 - @cornerstonejs/streaming-image-volume-loader@0.6.11
 - @cornerstonejs/tools@0.30.6

* fix: Use queryselector instead of firstChild to get svg-layer (#268)

* chore(release): publish [skip ci]

 - docs@0.7.17
 - @cornerstonejs/tools@0.30.7

* [wip] initial dicom-loader typescript conversion

* [wip] initial typescript conversion

* [wip] update types for tests

Co-authored-by: Alireza <ar.sedghi@gmail.com>
Co-authored-by: ohif-bot <contact@ohif.org>
Co-authored-by: Niclas do <32524613+doepnern@users.noreply.github.com>
Co-authored-by: Neil <neil.a.macphee@gmail.com>
Co-authored-by: Edward Son <edward_son@live.com>
Co-authored-by: edward65 <edward@afxmedical.com>
Co-authored-by: ramonemiliani93 <ramon.emiliani@umontreal.ca>
Co-authored-by: Ramon Emiliani <ramon@afxmedical.com>
Co-authored-by: 陈定苗 <359650098@qq.com>
Co-authored-by: chendingmiao <cdm@tomtaw.com.cn>
Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
Co-authored-by: Gabriel Lebaudy <gabriel.lebaudy@gmail.com>
Co-authored-by: Mustafa ÇİNİ <mcini@hotmail.com.tr>
Co-authored-by: James Manners <james@binary.com.au>
sedghi pushed a commit that referenced this pull request Jan 6, 2023
* fix: mouse-up should not unhighlight annotations (#305)

* fix: annotation highlighted and tooling for ellipticalROI

* update build

* fix tests

* chore(release): publish [skip ci]

 - docs@0.7.8
 - @cornerstonejs/tools@0.29.7

* fix: stack viewport flip scroll (#304)

* fix: use focal point for pan cache for stack viewport

* fix: pan dir with flip

* fix pan values while flipped

* update build

* apply review comments

* fix build

* chore(release): publish [skip ci]

 - @cornerstonejs/core@0.21.5
 - docs@0.7.9
 - @cornerstonejs/streaming-image-volume-loader@0.6.6
 - @cornerstonejs/tools@0.29.8

* feat: add referenceCursors tool (#275)

* added basic cursorCrosshairSync tool with example, TODO: for now cursorSync is displayed regardless of distance, create configurable distance and also sync the position of all viewports over which the mouse is not to scroll to a slice that is close to the currentMousePosition in 3d space

* addde stack syncing for StackViewport and syncing for volumeViewport on imageChange events, added configuration for max display distance

* refactored tool functions

* added comment to possible bug

* added configuration options to example

* changed look of crosshair to 4 lines with central space

* undid local tsconfig change

* undid yarn.lock changes

* added tool to example-info.json

* removed from example-runner because it broke build

* readded example and fixed typo

* readded example-info and changed example to trigger rebuild

* added cleanup for mouseoverElement when tool is disabled

* added cleanup when tool gets disabled, this does not get called when toolGroup gets destroyed, might cause remaining listeners

* applied naming changes, reworked adding annotation logic

* removed event listeners and moved logic to check for stack scrolling into rendering logic

* added planeDistanceToPoint to planar utilities

* added getClosestStackImageIndexForPoint

* rewrote logic to use onCameraModified

* updated example-info

* fixed bug with 0 being falsey

* added logic to remove cursor if wanted

* modified toolGroup so that setting a tool active only changes the cursor to default if there is no primary mouse cursor

* fixed bug not updating disable cursor

* fixed missing parentheses from merge

* readded scrollWheel scrolling and api changes

* fixed typos

* chore(release): publish [skip ci]

 - @cornerstonejs/core@0.22.0
 - docs@0.7.10
 - @cornerstonejs/streaming-image-volume-loader@0.6.7
 - @cornerstonejs/tools@0.30.0

* fix: ZoomTool fix for polyData actors with no imageData (#308)

* chore(release): publish [skip ci]

 - docs@0.7.11
 - @cornerstonejs/tools@0.30.1

* fix: If planar annotation is not visible, filter it (#318)

Co-authored-by: edward65 <edward@afxmedical.com>

* fix: filter planarFreeHandeROI based on parallel normals instead of equal normals. (#315)

Co-authored-by: Ramon Emiliani <ramon@afxmedical.com>

* fix: get correct imageData with targetId in BaseTool (#294)

* limit disabled element not need to render

* Update BaseTool.ts

fix: get correct viewport when there are multiple viewport with same stack data

Co-authored-by: chendingmiao <cdm@tomtaw.com.cn>

* chore(release): publish [skip ci]

 - docs@0.7.12
 - @cornerstonejs/tools@0.30.2

* fix: htj2k and keymodifier (#313)

* fix(htj2k):Support htj2k in the streaming volume loader

* fix(decodeImage):Fix htj2k image decode and mouse key modifiers

* Update for PR

* update ci build

* chore(release): publish [skip ci]

 - docs@0.7.13
 - @cornerstonejs/streaming-image-volume-loader@0.6.8
 - @cornerstonejs/tools@0.30.3

* fix: coronal view should not be flipped (#321)

* chore(release): publish [skip ci]

 - @cornerstonejs/core@0.22.1
 - docs@0.7.14
 - @cornerstonejs/streaming-image-volume-loader@0.6.9
 - @cornerstonejs/tools@0.30.4

* fix: bidirectional tool when short and long axis changes (#309)

* fix rotation for handles

* fix: short axis movement

* fix: bidirectional tool incorrect interaction

* chore(release): publish [skip ci]

 - @cornerstonejs/core@0.22.2
 - docs@0.7.15
 - @cornerstonejs/streaming-image-volume-loader@0.6.10
 - @cornerstonejs/tools@0.30.5

* fix(volumeViewport): Add optional scaling as the volume can be undefined (#323)

While trying to get the volume from the cache, it can be undefined so getting the scaling attribute would throw an error in that case.
This is a quick fix

* chore(release): publish [skip ci]

 - @cornerstonejs/core@0.22.3
 - docs@0.7.16
 - @cornerstonejs/streaming-image-volume-loader@0.6.11
 - @cornerstonejs/tools@0.30.6

* fix: Use queryselector instead of firstChild to get svg-layer (#268)

* chore(release): publish [skip ci]

 - docs@0.7.17
 - @cornerstonejs/tools@0.30.7

* [wip] initial dicom-loader typescript conversion

* [wip] initial typescript conversion

* [wip] update types for tests

* feat: enable having multiple instances of the same tool and add more seg tools (#327)

* feat: add floodFill and advanced brushTool

* feat: enable adding tool instances from a parent tool class

* fix threshold and brush size

* chore(release): publish [skip ci]

 - docs@0.7.18
 - @cornerstonejs/tools@0.31.0

* feat: Add new 3D volume viewport (#281)

* Extract common volume viewport functionality to base class

* Add viewport presets

* Add utility function for applying preset to volume actor

* Add new 3D volume viewport

* Add example for VolumeViewport3D

* feat: add presets dropdown to demo

* update example info json

Co-authored-by: Luccas Correa <luccascorrea@estudio89.com.br>
Co-authored-by: Alireza <ar.sedghi@gmail.com>

* chore(release): publish [skip ci]

 - @cornerstonejs/core@0.23.0
 - docs@0.7.19
 - @cornerstonejs/streaming-image-volume-loader@0.6.12
 - @cornerstonejs/tools@0.32.0

* fix: Add coplanar check in stackImageSync callback (#335)

* Add coplanar check in stackImageSync callback

* Refactoring function

* chore(release): publish [skip ci]

 - docs@0.7.20
 - @cornerstonejs/tools@0.32.1

* fix: could not access 'index' before initialization (#337)

* Avoid circular dependancy with vite build

* Nicer import

* chore(release): publish [skip ci]

 - docs@0.7.21
 - @cornerstonejs/tools@0.32.2

* [dicom-loader] update CornerstoneWadoLoaderOptions
to include optional params

* [dicom-image-loader] types update

* [dicom-image-loader] port wadoImageLoader tests
to monorepo

* [dicom-image-loader] port changes from cornerstoneWADOImageLoader commit id 9d71753
wayfarer3130 pushed a commit that referenced this pull request Jan 20, 2023
…ttier. (#275)

* chore: update eslint, prettier, modernize configs

* chore: run prettier on whole codebase
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants