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

lib-classifier: refactor single image viewer with VisXZoom #6390

Open
wants to merge 34 commits into
base: master
Choose a base branch
from

Conversation

mcbouslog
Copy link
Contributor

@mcbouslog mcbouslog commented Oct 16, 2024

Package

  • lib-classifier

Linked Issue and/or Talk Post

Describe your changes

  • create new single image subject viewer
  • create storybook for new single image subject viewer
  • rotate
  • invert
  • add VisXZoom for pan and zoom (includes mouse wheel zoom)
  • drag
  • drawing / InteractionLayer
  • placeholder image
  • check new single image subject viewer in test project
  • check new single image subject viewer with other viewers that use single image viewer
  • fix broken tests
  • add new tests
  • update related READMEs (i.e. Subject Viewer, likely others)
  • review mobile/pinch handling new functionality will be investigated and potentially a separate PR

Notes

How to Review

Helpful explanations that will make your reviewer happy:

Checklist

PR Creator - Please cater the checklist to fit the review needed for your code changes.
PR Reviewer - Use the checklist during your review. Each point should be checkmarked or discussed before PR approval.

General

  • Tests are passing locally and on Github
  • Documentation is up to date and changelog has been updated if appropriate
  • You can yarn panic && yarn bootstrap or docker-compose up --build and FEM works as expected
  • FEM works in all major desktop browsers: Firefox, Chrome, Edge, Safari (Use Browserstack account as needed)
  • FEM works in a mobile browser

General UX

Example Staging Project: i-fancy-cats

  • All pages of a FEM project load: Home Page, Classify Page, and About Pages
  • Can submit a classification
  • Can sign-in and sign-out
  • The component is accessible

New Feature

  • The PR creator has listed user actions to use when testing the new feature
  • Unit tests are included for the new feature
  • A storybook story has been created or updated
    • Example of SlideTutorial component with docgen comments, and its story

Refactoring

  • The PR creator has described the reason for refactoring
  • The refactored component(s) continue to work as expected

@coveralls
Copy link

coveralls commented Oct 16, 2024

Coverage Status

coverage: 78.042% (+0.2%) from 77.873%
when pulling c58b452 on lib-classifier_mouse-scroll-zoom
into fbab1d8 on master.

@mcbouslog mcbouslog changed the title [DRAFT] lib-classifier: refactor single image viewer with VisXZoom lib-classifier: refactor single image viewer with VisXZoom Nov 14, 2024
@mcbouslog mcbouslog marked this pull request as ready for review November 14, 2024 15:52

import SVGImage from '../SVGComponents/SVGImage'

function calculateAdjustedViewBox({ naturalWidth, naturalHeight, transformMatrix }) {
Copy link
Contributor Author

@mcbouslog mcbouslog Nov 14, 2024

Choose a reason for hiding this comment

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

I think this calculation needs additional work, specifically to handle zoomed out panning. When zooming out, then panning, the image jumps around erratically.

@mcbouslog mcbouslog requested review from a team and goplayoutside3 November 14, 2024 20:51
Comment on lines +75 to +85
<VisXZoom
height={naturalHeight}
panning={panning}
setOnPan={setOnPan}
setOnZoom={setOnZoom}
width={naturalWidth}
zoomConfiguration={DEFAULT_ZOOM_CONFIG}
zoomingComponent={SingleImageCanvas}
zooming={zooming}
{...singleImageCanvasProps}
/>
Copy link
Contributor Author

@mcbouslog mcbouslog Nov 15, 2024

Choose a reason for hiding this comment

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

Copy link
Contributor

@goplayoutside3 goplayoutside3 left a comment

Choose a reason for hiding this comment

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

I started reading through each viewer and testing their UX. Because this will be an in depth review and lots of moving parts, I'm pausing with a couple of questions and will resume once they're resolved.


- [`VisXZoom`](components/SVGComponents/VisXZoom) - An extension of the VisX library's pan and zoom functional component. Uses a transformation matrix, boolean control for zoom and pan individual to turn the functions on and off as needed, has configurability for zoom direction, minimum zoom, maximum zoom, zoom in value, and zoom out value, and supports zoom to point. Currently implemented with the `ScatterPlotViewer`, the scatter plots used by the `VariableStarViewer`, and the scatter plot used by `DataImageViewer`. It uses the [`ZoomEventLayer`](components/SVGComponents/ZoomEventLayer) which is a transparent SVG rectangle that has event listeners for double click, on wheel, on key down, on mouse down, on mouse up, on mouse enter, and on mouse leave. Subject viewers using this should set as props the width, height, left and top points of the SVG area to render, as well as, the child subject viewer component to render, a zoom configuration object, and optionally a custom [constrain](https://airbnb.io/visx/docs/zoom#Zoom_constrain) function enabling the ability to constrain pan and zoom directionality and/or pan dimensions. An example of this implementation is used with the [`ZoomingScatterPlot`](components/ScatterPlotViewer/ZoomingScatterPlot). The zoom configuration can be set in the workflow configuration [`subject_viewer_config`](https://github.com/zooniverse/front-end-monorepo/blob/master/docs/arch/adr-27.md) object or in the JSON of a JSON subject. (NOTE: TODO is to finish building out support for configuration being set on the workflow configuration object).
- [`SVGPanZoom`](components/SVGComponents/SVGPanZoom) - A port of the PFE pan and zoom functionality being used with the `SingleImageViewer` and subsequently the `MultiFrameViewer` and anywhere else the `SingleImageViewer` may be used. It does scale transformations on the SVG view box.
- [`VisXZoom`](components/SVGComponents/VisXZoom) - An extension of the VisX library's pan and zoom functional component. Uses a transformation matrix, boolean control for zoom and pan individual to turn the functions on and off as needed, has configurability for zoom direction, minimum zoom, maximum zoom, zoom in value, and zoom out value, and supports zoom to point. Currently implemented with the `SingleImageViewer`, the single image viewer is used by `DataImageViewer`, `FlipbookViewer`, `ImageAndTextViewer`, `MultiFrameViewer`, and `SeparateFramesViewer`, as well as the `ScatterPlotViewer`, the scatter plots used by the `VariableStarViewer`, and the scatter plot used by `DataImageViewer`. It uses the [`ZoomEventLayer`](components/SVGComponents/ZoomEventLayer) which is a transparent SVG rectangle that has event listeners for double click, on wheel, on key down, on mouse down, on mouse up, on mouse enter, and on mouse leave. Subject viewers using this should set as props the width, height, left and top points of the SVG area to render, as well as, the child subject viewer component to render, a zoom configuration object, and optionally a custom [constrain](https://airbnb.io/visx/docs/zoom#Zoom_constrain) function enabling the ability to constrain pan and zoom directionality and/or pan dimensions. An example of this implementation is used with the [`ZoomingScatterPlot`](components/ScatterPlotViewer/ZoomingScatterPlot). The zoom configuration can be set in the workflow configuration [`subject_viewer_config`](https://github.com/zooniverse/front-end-monorepo/blob/master/docs/arch/adr-27.md) object or in the JSON of a JSON subject. (NOTE: TODO is to finish building out support for configuration being set on the workflow configuration object).
Copy link
Contributor

Choose a reason for hiding this comment

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

Just out of curiosity is the 'todo' at the end in a Github Issue or referenced somewhere else in the code?

Copy link
Contributor Author

@mcbouslog mcbouslog Nov 19, 2024

Choose a reason for hiding this comment

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

I think #2110 is the related issue. I'm not sure the NOTE belongs in the README though. I could remove the NOTE or update it to include the specific issue?

const imageLocationUrl = subject?.locations[currentFrame]?.url

const { img, error, loading, subjectImage } = useSubjectImage({
src: imageLocationUrl,
Copy link
Contributor

Choose a reason for hiding this comment

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

Changing this from defaultFrameLocation to imageLocationUrl means useSubjectImage() hook is called on every frame because imageLocationUrl is determined by currentFrame rather than defaultFrame. This is undesired behavior because it directly calls onSubjectReady() every time a volunteer flips to a new frame. onSubjectReady() resets the rotation between frames and pushes new metadata to the classification.

Regarding rotation, we want volunteers to be able to rotate the subject and 'play' the flipbook with a subject rotated. rotation should not be reset in between every frame.

I noticed the metadata bug because earlier this year it was introduced by #5754 and fixed by #5952. On this branch, when I used my flipbook test project (listed in the FEM active projects for reference), this is what a submitted classification looks like when I flipped through 12 frames. Huge metadata causes classification exports to hang.
Screenshot 2024-11-18 at 3 32 35 PM


Questions - Is the change from defaultFrameLocation to imageLocationUrl intentional and required for the new SingleImageViewer with VisXZoom? How can each viewer ensure onSubjectReady() is called only once per subject, and not per frame? I didn't look into ImageAndTextViewer, MultiFrameViewer, and SeparateFramesViewer in detail yet, but I want to include those in this discussion too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Questions - Is the change from defaultFrameLocation to imageLocationUrl intentional and required for the new SingleImageViewer with VisXZoom?

I don't think it's required. I'll try refactoring so onSubjectReady (onReady passed from SubjectViewer) is only called once per subject and report back.

@eatyourgreens
Copy link
Contributor

eatyourgreens commented Nov 22, 2024

When Sarah converted the single image viewer to VisXZoom (back in 2020, I think), it broke all the drawing tools. This PR breaks all the drawing tools too, in (I think) the same way. Should I add videos/issue descriptions, or is this still a work in progress?

To see this, try drawing while zoomed in on this branch, particularly with any tool that uses dragging motions/gestures.

Here's a screen recording with the rectangle tool, dragging the resize handles while zoomed in. You can also reproduce this with the cat face ellipse tool in I Fancy Cats.

Screen.Recording.2024-11-22.at.14.33.49.mov

I think one big barrier to a major refactor was a lack of tests, in turn due to lack of SVG support in jsdom. It's really easy to break the drawing tools without being alerted by failing CI tests.

@eatyourgreens
Copy link
Contributor

eatyourgreens commented Nov 22, 2024

There's an undocumented feature, used by Seabird Watch and Penguin Watch, which you should add to your manual drawing tool tests.

When you're making a large number of marks, you don't want to be constantly switching between zoom mode and drawing mode. For this reason, you can control pan-and-zoom with the keyboard, from a focused drawing mark.

To see this in action, try placing marks, while zooming and panning with the keyboard, at either of the following links:

You should be able to mark a penguin, then zoom and pan from the keyboard while continuing to mark penguins on the zoomed picture (on crowded pictures, you really need to zoom in, so this feature isn't optional.)

Screen.Recording.2024-11-22.at.10.15.02.mov

This PR breaks that keyboard accessibility for drawn marks.

@eatyourgreens
Copy link
Contributor

eatyourgreens commented Nov 22, 2024

One last comment: on this branch, drawn marks scale up with the image when you zoom in. Drawn marks should ignore the scale transformation and stay the same size at all zoom levels (see #6066 for one attempt to fix this for stroke widths. You can also style SVG shapes so that they are independent of scale.)

@mcbouslog
Copy link
Contributor Author

Thank you @eatyourgreens , the comments and videos are very helpful! To summarize I think you've identified 3 issues:

  1. marks with drag and move handles aren't scaled and working as expected
  2. keyboard pan and zoom should be enabled in annotation mode
  3. marks aren't properly scaled zoomed in or out

I'll report back on progress.

@eatyourgreens
Copy link
Contributor

eatyourgreens commented Nov 22, 2024

Thank you @eatyourgreens , the comments and videos are very helpful! To summarize I think you've identified 3 issues:

  • marks with drag and move handles aren't scaled and working as expected

You're welcome. For that first one, I can also reproduce it with the point tool, without drag handles. Drop a point on the image somewhere, then drag and move it while zoomed in. The pointer movement, within the subject viewer, isn't properly mapped to the image. It looks like the pointer coordinates are still scaled to the subject viewer dimensions, not the new, larger image dimensions. Dragging only works when the subject viewer and image are the same size. Could be a missing transformation somewhere on the <rect> element that acts as the drawing canvas. Maybe it isn't resizing to match the image size?

@mcbouslog
Copy link
Contributor Author

I can recreate the noted 2 and 3 issues, but I'm having trouble recreating 1 (marks with drag and move handles aren't scaled and working as expected)? I'm using Chrome v131, but I don't think it'd be browser specific.

ZoomInAndDrawRectangle.mp4

@eatyourgreens
Copy link
Contributor

eatyourgreens commented Nov 23, 2024

Sorry, I forgot to say that my video was Firefox 132. SVG implementations do vary between browsers, unfortunately.

EDIT: I think the drawing tools are only using bits of the SVG 2 spec that are well-supported across the major browser engines.

@eatyourgreens
Copy link
Contributor

eatyourgreens commented Nov 23, 2024

I just tried a few different browsers. Scaling and dragging seems to be ok in Chrome or Safari, but broken in Firefox.

@eatyourgreens
Copy link
Contributor

eatyourgreens commented Nov 23, 2024

This might be relevant. Dragging interactions get the current transformation matrix from a svg element stored in SVGContext here:

Creating a mark here gets the CTM from the DrawingCanvas component in the InteractionLayer component, which is an SVG rect that handles all the pointer events that create marks.

These refs don’t point to the same element, so that could account for differences in behaviour between creating a mark and dragging a mark. They probably should be using the same element and transformation matrix. I don't think I've noticed this before. At least, I can't find an issue for it, and I'm sure I would have opened an issue for something like this.

@eatyourgreens
Copy link
Contributor

eatyourgreens commented Nov 23, 2024

Update: dragging a zoomed-in mark is broken in Firefox on the production release. I'll open an issue. It's not a bug that's been introduced on this branch. The reason is that the draggable decorator is getting the screen CTM from the wrong DOM node, so not transforming pointer coordinates correctly.

Update to the update:

transform={rotationTransform}
>
<svg
ref={canvasLayer}
Copy link
Contributor

@eatyourgreens eatyourgreens Nov 23, 2024

Choose a reason for hiding this comment

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

This ref is causing the broken dragging behaviour in Firefox. It causes dragging to get the screen CTM from this svg element, not the rect that's actually used for drawing. I've removed it in #6491, in favour of setting svgContext.canvas inside the InteractionLayer component.

maxWidth={maxWidth}
tabIndex={0}
viewBox={viewBox}
xmlns='http://www.w3.org/2000/svg'
Copy link
Contributor

Choose a reason for hiding this comment

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

xmlns is ignored by HTML parsers, so you can remove it. See the note at the top of the MDN SVG element page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Development

Successfully merging this pull request may close these issues.

Add mouse wheel zoom to image subject viewers
4 participants