-
Notifications
You must be signed in to change notification settings - Fork 29
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
Change Flipbook and SeparateFrames viewers behavior to not clone drawing marks #5754
Conversation
3ba70fd
to
2296e8c
Compare
This refactor took quite a bit of thinking through state and how it updates + flows in the downstream components. I at first was trying to create local state and pass through + update the local state of the frame within the components before realizing I could piggyback on the way the frames are rendered to pass state. This made the code changes relatively minor (first couple tries weren't!). This currently does not have the "clone marks" functionality from issue #5493 - that's next. |
This looks like a good starting point because it's essentially changing FlipbookViewer's frame handling into MulitFrameViewer's frame handling. MultiFrameViewer pulls its current frame from the store because transcription subject frames are always viewed one at a time. This is fine for flipbook when a project owner doesn't want to clone marks across frames. I tried out the deployed test project and have some questions:
|
Just to double check since this PR is still marked as a draft - Is it ready for review or do some of the review points above still need to be addressed? |
6e70e93
to
f7f5bd0
Compare
@goplayoutside3 - this is ready for review |
f7f5bd0
to
55b03c7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking better! I tested with your project locally by running app-project. Existing flipbook projects like Daily Minor Planet also work as expected. See notes below about Correct a Cell.
The PR description is missing some relevant review things so I also tested:
- 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 PR creator has described the reason for refactoring
- The PR creator has listed user actions to use when testing the new feature
- Unit tests are included for the new feature
- The refactored component(s) continue to work as expected
Blocking Things
-
Please double check your changes to
useFreeHandLineReductions
against live projects that use the feature. This is my first experience reading the code that handles reductions, so I need a bit more guidance on how to test, but I think I already found a bug. I looked at the Correct a Cell workflow on production, and each subject loads with reductions on top of its image. However, when I run Correct a Cell locally on this branch, and submit a classification, all of the subsequent subjects loaded are showing 0 reductions. Perhaps I got a few subjects in a row that don't actually have reductions, but there needs to be some manual testing and documentation in this PR about how changes affect or don't affect live freehand line projects. -
I recommend changing the PR title to "Change Flipbook and SeparateFrames viewers behavior to not clone drawing marks" to communicate the changes here.
...onents/Classifier/components/SubjectViewer/components/SingleImageViewer/SingleImageViewer.js
Show resolved
Hide resolved
packages/lib-classifier/src/store/WorkflowStore/Workflow/Workflow.js
Outdated
Show resolved
Hide resolved
55b03c7
to
392ce10
Compare
@goplayoutside3 - ready for re-review. I saw the bug you found regarding not being able to submit multiple classifications after changing subjects. I moved all this code into the SubjectStore instead of the Workflow store (makes more sense to tie subject state in the subject store). I modified the test project to have 4 separate subjects so you can test the issue resolution within the current project. I also tracked down the source of the LineControls zoom size issue.. It's not specific to this project but a more contained, separate bug. I created Issue #5833 to document this. I couldn't quite figure out how to fix it so I'd like to brainstorm with the team about ideas when it makes sense. |
392ce10
to
8c35319
Compare
Thanks for the update! I'll take a look at this today and linked the corresponding PFE PR zooniverse/Panoptes-Front-End#6996 to be tested together. Regarding #5833, I don't know of a solution of the top of my head, but after reviewing this PR I'll see if I can add any ideas to that Issue. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The recent changes fix the issue with loading reductions as I classify multiple subjects 👍 However, now we've got a problem with the frame
prop being too rigid.
While I think it's fine to force frame
to 0
in useFreehandLineReductions()
based on whether a workflow clones marks or not, forcing the frame
prop passed to viewer components has unintended consequences in the flipbook because the navigation controls and functionality of the viewer rely directly on that prop.
The Flipbook Viewer and Separate Frames Viewer should be free to react to the frame
prop passed to them from the store. Instead of writing a conditional in FlipbookViewerContainer, I think a good strategy would be to modify frame
in useFreehandLineReductions()
hook or FreehandLineReductions.js
store or a new helper function as needed.
Maybe there needs to be separation of the prop that determines "currently viewed frame" and the way frame
is recorded per mark. For instance, on production Flipbook Viewer has local state (useState
) as a strategy for "currently viewed frame" especially because projects like Daily Minor Planet autoplay the carousel, and rather than update the mobx-state-tree store on each render, only local state changes. Building on the existing architecture, is there a way to load freehand line reductions, put them in the store, and then each time the frame changes in FlipbookViewer use a helper function to grab the corresponding line reductions from the store? The store would act as source of truth for marks, and each mark object has a frame
(i.e tool.createMark
). In this scenario the same helper function would be called in a SeparateFrame component who's frame
prop is passed as the index
of the locations
array for the subject.
enableInteractionLayer={enableInteractionLayer} | ||
frame={(multiImageCloneMarkers) ? 0 : frame} // workflow.multiImageCloneMarkers = true, then we force all frames to be 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line introduces two problems:
- When
multiImageCloneMarkers
is configured to true,frame
is forced to0
here, and the flipbook controls no longer work because frame is always0
. I tested this by loading your staging project via the PFE lab (https://www.zooniverse.org/lab/1993/workflows/3760?env=staging&pfeLab=true), I enabled "Clone markers in all frames", and then ran app-project locally. While the drawn marks are cloned correctly across frames in the separate frames view, I can't play or navigate between frames in the flipbook viewer. - By forcing frame to
0
, it also overwrites default frame configuration that project teams can introduce per subject. See the example in FlipbookViewer's storybook here. The SubjectViewerStore detects if there'smetadata.default_frame
and setsframe
accordingly, but should not be overwritten here in FlipbookViewerContainer. I suggest either creating stories or subjects in your test project with a configuration combo of default frame and clone / not clones marks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reverted this back - I didn't understand the interdependencies here so this helps me understand more contextually how this prop is used further in the component rendering pipeline :)
I reverted the changes to the That said, I do think this becomes the right area to modify because it's the logic that controls which marks are rendered on which frame. Since the Finally, I had created a second project for Also, not sure why coverage is failing. Running the classifier tests locally has no issues. |
Not sure why tests failed either, but since this branch is several weeks behind master I'm going to update the branch and trigger Actions again. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While it's a good idea not to force frame number anymore, a couple of bugs still exist in the current implementation:
- When I draw on the "don't clone marks" project while on separate frames, and submit my classification, the drawn mark is missing
pathX
andpathY
again (seen in the browser console logs). This happened inconsistently on the "clone marks" project in separate frames view as well. - Do all of the subjects in this project have freehand line reductions? I do see the reductions load when using the flipbook, but after one classification with separate frames, I don't see any reductions load. This and the first bug seem specific to separate frames view.
- InteractionLayer is used across lots of subject viewers, not just flipbook and separate frames. I think by modifying the code in that file, it's unintentionally broken multi-image subjects that use the MultiImageViewer. For example, load this transcription test project, draw a mark on the first frame and the mark will show up on the 2nd frame too. That project does not have "clone markers in all frames" enabled.
I think we might need to back up to the big picture of the requested feature.
Would you be willing to write an overview of the ideal data flow for this feature? For example, an idea of where marks are stored once reductions are loaded, where marks are stored when a volunteer draws a new mark on a subject viewer, how each of those marks obtains its frame
value, how a subject viewer keeps track of its current frame, and lastly how to filter stored marks in the subject viewer depending on whether "clone marks" is true or false.
Manual testing of data when submitting a classification is crucial too. Unit tests can help for various cases, but it's helpful to load each of your test projects, draw on flipbook, draw on separate frames, and examine the submitted classification object in the console to double check the annotation. (Live FEM drawing projects with demo mode enabled is an option too)
const newMarks = | ||
shownMarks === SHOWN_MARKS.NONE ? marks.slice(hidingIndex) : marks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To answer your question about SHOWN_MARKS.NONE
, this bit of code handles the MetaTools UI below the subject viewer. The helper function for SHOWN_MARKS
is here and pertains to the HidePreviousMarksButton component.
Volunteers can toggle marks on and off or filter to see their own without the ones pulled in from caesar.
For example in Correct a Cell, there are only two options: Hide Previous Marks or Show Previous Marks. In transcription projects, there's a third option to filter only your user's marks.
1) Not having pathX | pathY Below are four videos wherein I can't replicate this issue in either cloneMarks enabled or disabled. I was only able to see this behavior after classifying the 4 subjects and getting the "you've already seen this subject" banner. The four videos are the four permutations - flipbook+clone, flipbook+noclone, separateframes+clone, separateframes+noclone. 2) Does each subject have reductions? Yes, there are only 4 subjects. Each subject has 2 marks/reductions. Each subject has a different combination of frames for each subject (that's how they're different)... What I admit is a bit confusing is that the two marks have the same coordinates regardless of subject (simplicity of copy/paste in generating the data). I couldn't replicate the issue of not loading reductions in the 4 videos below.. I only didn't see the reductions once the "you've already seen this subject" banner appeared. 3) Interaction layer broke Multiframe (transcription project) I tested this locally and replicated the issue.. The problem is that the MultiFrameViewerContainer was not passing the frame property into the SingleImageViewer, which then renders the InteractionLayer. This resulted in all of the 4) Big Picture Control Flow
5) Big Picture Questions from Above Where are marks stored once reduction is loaded? Where are marks stored when volunteer draws a new mark? How do each marks obtain their frame value? How does the subject viewer keep track of its current frame? How to filter stored marks in the SubjectViewer? https://github.com/zooniverse/front-end-monorepo/assets/733986/36531ef0-8b2c-478e-9701-0b7069ab6b35 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I expanded on your data flow explanation after more investigation, and opened a draft PR #5851 to illustrate the points below. I think #5851 is on a more sustainable track than the changes in this PR at the moment. Please feel free it pull into this one, or continue working toward 5493 on my draft PR ( I can approve and merge that one if eventually needed ).
Fetching Reductions into the mobx-state-tree (MST) store
I only didn't see the reductions once the "you've already seen this subject" banner appeared.
I don’t think it’s expected behavior to not display caesar reductions if a volunteer has already seen a subject. We don’t want to waste a volunteer’s time, but it seems like a subject image and its reductions should always be coupled. That’s the precedent set by the transcription task.
Drawing tools projects’ reductions (only freehand line for now) are stored in the Subject.js MST store as Subject.caesarReductions
via useCaesarReductions
hook, which is called when a volunteer loads a new subject or new workflow. Following that logic, if a subject in the panoptes database has reductions, loading the subject on the frontend should always display its reductions too. Also, no need to replicate caesarReductionsLoadedForSubject
in the SubjectStore because Subject.caesarReductions
already exists.
Displaying Reductions per current frame
As mentioned above, freehand line reductions are fetched via useCaesarReductions
hook and stored as Subject.caesarReductions
.
A potential data flow: useCaesarReductions
grabs all reductions for a subject regardless of “currently viewed frame” in the subject viewer. Then, when Subject.caesarReductions
(MST store) is referenced for display in the flipbook or separate frames viewers, a helper component within those viewers accomplishes desired filtering.
- A conceptual example of filtering marks can be seen in the TranscribedLines component. This component grabs all
lines
(caesar reductions stored in MST store) and filters them based on whether a line still needs volunteer input or it’s complete. - The DrawingToolMarks component serves the same purpose for drawing tools workflows as the TranscribedLines component does to transcription workflows.
- DrawingToolMarks is a child component of InteractionLayer and it gets passed an array of
marks
from InteractionLayerContainer, which grabsmarks
from the active task type (drawing or transcription) stored asClassification.annotations
in the MST store. - ❓ I think DrawingToolMarks and
Subject.caesarReductions
in the MST store are connected viauseFreehandLineReductions
hook in DrawingToolMarksConnector? I don’t have a good understanding ofuseFreehandLineReductions
hook by reading the code, but in theory, I think this hook could be passed aframe
param (the sameframe
passed to DrawingToolMarks) and it could filter reductions by frame. - ❓ Some explanations that would help me understand - What is
useFreehandLineReductions
accomplishing, and why does it return only atask
? I don’t understand marksUsed either.
Side note: In an ideal scenario, a project team who wants to clone all marks has reductions where each mark is frame: 0
. If a project team’s reductions does not have frame: 0
on every mark and they want to clone marks, that might be something we coerce on the frontend for special cases (not in this PR). Conversly, when a project team does not want to clone frames, their reductions ideally include marks that specify which frame
they should be displayed on.
Tracking Currently Viewed Frame
MultiFrameViewerContainer never passed the frame={frame} prop down. Same with SeparateFramesViewer. This, to me, is a bug that went unnoticed.
This is actually intentional because MultiFrameViewer was built to rely on SubjectViewerStore for frame
, but SeparateFramesViewer intentionally does not.
MultiFrameViewer is a component designed to show only one frame at a time, and is exclusively used for transcription tasks where play/pause looping through multiple frames is never needed. MultiFrameViewer uses the SubjectViewerStore as a frame
“source of truth”. This is fine because every frame in a transcription multi-image subject has different caesar reductions. Nothing is cloned across frames in transcription workflows. It’s tempting to simply use MultiFrameViewer for all workflows with drawing tools and multi-image subjects, but the separate frames feature, thumbnails below the subject, along with PFE parity are the motivation behind enabling drawing tools on FlipbookViewer and SeparateFramesViewer instead.
In contrast to MultiFrameViewer’s reliance on the MST store, FlipbookViewer currently uses local state to keep track of which frame is displayed. This works well especially for projects like Daily Minor Planet where the autoplay looping feature is crucial to a volunteer’s classification. Not all projects use the play/pause feature of flipbook, but when enabling drawing tools on the flipbook, we don’t want to degrade the UX for non-drawing flipbook projects by pushing a new frame
value to the MST store when flipbook is looping 4 times per second.
Passing a locations
index to SeparateFrame does works well for frame
because it can be further passed onto InteractionLayer to track which separately displayed frame a volunteer clicked on to generate a new mark.
Newly drawn marks + current frame
Now we can consider how SubjectViewer > InteractionLayer > DrawingToolMarks plays a role here. If FlipbookViewer or SeparateFrame pass frame
to InteractionLayer, the DrawingToolMarksConnector and DrawingToolMarks components can filter marks.
When a volunteer draws a new mark on the InteractionLayer, the mark is stored as an Annotation type on the Classification store (Classification.annotations
). The Annotation model differs depending on the task type and tool type. For instance, when the active drawing tool is a freehand line, the Annotation model has marks
as types.map(FreehandLine)
. A new mark utilizes createMark
from the Mark model which takes a frame
. This frame
can come from InteractionLayer regardless of InteractionLayer’s parent component.
DrawingToolMarks gets passed marks
from InteractionLayerContainer which got them from Classification.annotations
. Therefore, as long as createMark
is called with the desired frame
, then DrawingToolsMarks can be passed a “currently viewed frame” prop and filter the marks
appropriately.
InteractionLayer also has a PreviousMarks component for displaying marks from previous workflow steps during the same classification (i.e Elephant ID style workflows). The PreviousMarks component currently grabs frame
from the SubjectViewerStore to determine which marks to display. This pertains to classification.previousInteractionTaskAnnotations
in the MST store. It does not pertain to caesar reductions. PreviousMarks could be modified to get frame
from its parent InteractionLayerContainer rather than grabbing it from the MST store (setting up a workflow with drawing tools and multiple steps is needed to test PreviousMarks functionality).
To Clone or Not to Clone
You had a similar implementation previously in this PR, but it was too rigid for the flipbook looping feature. In order to clone or not clone marks, I think frame
can be coerced to 0
in InteractionLayerContainer instead of its parent viewer components. When not cloning marks, the currentFrame
prop from FlipbookViewer or SeparateFramesViewer is passed to InteractionLayer for creating marks, otherwise frame=0
is passed to InteractionLayer.
Again, frame
should be used accurately in createMark
regardless of InteractionLayer’s parent component. In #5851 strategy, FlipbookViewer and SeparateFrame has its own local state for “currently viewed frame”, yet when we want to clone marks, I’m telling InteractionLayer that frame
is always 0
, even if the currently viewed frame is not 0
in FlipbookViewer or SeparateFrame.
Next Steps
The main follow-up steps to complete this feature are the addition of unit tests to #5851 (fix one of PreviousMark's tests too), and confirming that #5851 strategy can work with freehand line reductions in all cases. I did not test many live projects in #5851 yet, and left in-code comments to be filled in.
With your test projects, I recommend creating multiple workflows with a set of multi-image subjects (you don’t need multiple projecs, just multiple workflows). Test combos of drawing tasks with multiple steps (like Elephant ID), cloning or not cloning marks, and double check that live Zooniverse projects (Correct a Cell) won’t be affected. I also think an ADR summarizing this background info and final implementation would be super helpful.
@@ -68,10 +69,11 @@ subject, | |||
{children} | |||
{enableInteractionLayer && ( | |||
<InteractionLayer | |||
scale={scale} | |||
frame={frame} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something to be careful with React - here you’re passing frame
as a prop from SingleImageViewer to InteractionLayer, while InteractionLayerContainer is also grabbing frame
from the SubjectViewerStore. There should be only one source of frame
for InteractionLayer at any given time. This is the crux of deciding where a newly drawn mark gets its frame
property from.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You’re passing
frame
as a prop from SingleImageViewer to InteractionLayer, while InteractionLayerContainer is also grabbing frame from the SubjectViewerStore. There should be only one source offrame
for InteractionLayer at any given time.
This comment is still relevant with your latest changes~
The component InteractionLayer
imported into SingleImageViewer.js
is actually from InteractionLayerContainer.js
. See the default export file here:
Line 1 in 2340ef2
export { default } from './InteractionLayerContainer' |
Can frame
be removed from InteractionLayerContainer's storeMapper()
so there's not two sources of the frame
prop?
@@ -29,6 +29,7 @@ const SubjectStore = types | |||
.model('SubjectStore', { | |||
active: types.safeReference(SubjectType), | |||
available: types.optional(AvailableSubjects, () => AvailableSubjects.create({})), | |||
caesarReductionsLoadedForSubject: types.array(types.string), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no need to replicate caesarReductionsLoadedForSubject
in the SubjectStore because Subject.caesarReductions
already exists.
Thank you for the very thorough comment! High level, there's a couple things to clarify, and then I think the crux of the question is who is responsible for setting the frame property.
I can see continuing to show caesar reductions if a volunteer has already seen a subject. We should probably have a broader conversation about why we do the "you've already seen this subject" banner in the first place, but that's a sidebar conversation. When we use the
A slight misunderstanding to clarify here is that
In theory, the hook could be passed the frame, but that wouldn't mitigate the issue of 1) what happens if the caesar mark has already been loaded / created and 2) what happens if the caesar mark has been deleted? The way this currently works to me is a little unnecessary... What I'd imagine instead of having this code run on every interaction layer render it instead has the reductions loaded via the store and then the UI listens for changes to the store so that the re-render only happens once and we're not running a bunch of code on everything that causes the Side note about forcing
I'm having a hard time understanding why we rely on the SubjectViewerStore as the source of truth so deep into the component stack instead of where it's actually needed/used (the Viewer level). To me, the components themselves dictate the data inheritance structure. The Viewer (which chooses Flipbook, MultiImage, etc) will have a subject that may or may not have multiple frames. The attribute of What I'm proposing as the clear, unambiguous way of handling this is that On your point about degrading non-drawing flipbook projects by pushing a new I'm not familiar with PreviousMarks so I'll have to dive into that more separately.
I think the biggest reason to advocate for the changes I'm suggesting is that this solution handles the variety of Flipbook, SeparateFrame, and MultiFrame attributes that get passed to the SingleImageViewer (which does all the heavy lifting in all 3 cases). The biggest drawback to the SubjectViewerStore as a source of truth for |
a9231e6
to
4691a2c
Compare
Did a bit of refactor, cleanup, and lots of testing.. Updates:
Update Tasks:
Issues for Future PR:
|
4691a2c
to
5576795
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Manual testing is looking good! I've left some questions on specific changes, but to address the following:
Previous Marks are carried forward from a previous step to future steps (not sure if this is desired behavior)
This is desired behavior. When a volunteer draws a mark on a step, and then goes to the next step, the previously drawn marks should remain as non-interactive. A live example of this is Elephant ID where the first step is to draw a point, and then answer a series of questions. The drawn point remains on the subject image while a volunteer answers the next steps' questions.
When I load https://local.zooniverse.org:3000/projects/kieftrav/freehand-line-multiframe/classify/workflow/3760, I draw on the subject image during the first step, and then click "Next" to the second step, the marks I've drawn remain. To my knowledge there hasn't been a Zooniverse project that involves freehand line reductions and multiple steps. Therefore, I don't know what the expected behavior is for those reductions, but I'll note that in the transcription project Corresponding with Quakers (which has transcription reductions), the caesar reductions only show on the first step. The second step asks volunteers a multiple choice question.
@@ -68,10 +69,11 @@ subject, | |||
{children} | |||
{enableInteractionLayer && ( | |||
<InteractionLayer | |||
scale={scale} | |||
frame={frame} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You’re passing
frame
as a prop from SingleImageViewer to InteractionLayer, while InteractionLayerContainer is also grabbing frame from the SubjectViewerStore. There should be only one source offrame
for InteractionLayer at any given time.
This comment is still relevant with your latest changes~
The component InteractionLayer
imported into SingleImageViewer.js
is actually from InteractionLayerContainer.js
. See the default export file here:
Line 1 in 2340ef2
export { default } from './InteractionLayerContainer' |
Can frame
be removed from InteractionLayerContainer's storeMapper()
so there's not two sources of the frame
prop?
findCurrentTaskMarks({ stepKey, tasks, frame }) { | ||
if (stepKey === undefined || tasks === undefined || frame === undefined) { | ||
findCurrentTaskMarks({ stepKey, task }) { | ||
if (stepKey === undefined || task === undefined || task.type !== 'drawing') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By adding task.type !== 'drawing'
here you're assuming that the current task a volunteer is being asked to complete is to draw something - is that correct?
Could there be a scenario where a subject is loaded with its freehand line reductions and the task is to answer "Are there machine generated lines on the image? Yes/no"? It's a contrived example, but should the task type really be coupled with findCurrentTaskMarks
? I'm not sure about the correct answer, but wanted to ask for more details about why task.type
must be drawing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a very good point! When I built the code to fetch the reductions I made it as specific as possible to the step / task combo but there was nothing that said that was a requirement.. Your example is a good one where the mark should be loaded for a 1st task which is a question and a 2nd task which is then the actual drawing task. Code updated!
setIsUsed(index) { | ||
self.isUsed[index] = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this function for? I don't see setIsUsed()
anywhere else in the codebase, and looks like this PR removes it from useFreehandLineReductions()
hook.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cruft. Removed.
tasks: step.tasks, | ||
frame, | ||
task: step.tasks[0], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes in this file are still a bit confusing to me. The purpose of useFreehandLineReductions()
is to fetch reductions via useCaesarReductions()
, then filter the reductions for marks that belong to the current step
. Is that the correct understanding? Or are the changes in this file intended to filter marks per task
too, and why?
I think my confusion stems from knowing that in the FEM mobx store, each workflow has a map of steps (workflow.steps
). Each step
has an array of taskKeys
, which maps to the workflow's tasks
array. Essentially, tasks are grouped into steps, and having more than one of either is optional. There's further explanation in ADR 5, and ongoing work in the fem-lab to build the corresponding UI.
This question might help clarify too: Why is FreehandLineReductions.js's findCurrentTaskMarks
concerned with the task.type
? My understanding is that the FreehandLineReductions mobx store model is relevant when reductions
fetched via useCaesarReductions()
hook have a reducer
that equals 'machineLearnt'. Why does reductions.data
need to be filtered based on task type, tool type, etc?
Apologies for harping on this topic 😆 but because there are several changes in this PR to freehand line code files, I want to be sure a clear understanding is documented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The useFreehandLineReductions()
fetches reductions via useCaesarReductions()
that get loaded per subject. They then were filtered for the current step
, task
, and tool
. This was meant to ensure that's what the researcher wanted and is verifiable by the caesar data.
That said, as per your comment above with the "contrived example", I don't think that should be necessary. I think the constraint can instead be on the step
it gets loaded and that's it. This is a good example to bring up to Cliff about how we load caesar data across the board and may be part of an ADR of some kind.
tool.createMark({ frame, id, toolIndex, pathX, pathY }) | ||
} | ||
const { frame, markId: id, toolIndex, pathX, pathY } = caesarMark | ||
const task = step?.tasks[0] // we only get access to one task at a time in a step |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line is related to my above questions in this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As per the comments above, this is being removed so it focuses exclusively on the step
. The only benefit to also filtering by task is if you had 3 tasks in a step and you only wanted the marks to show on the 3rd task. The reason the code operated this way is that getting the step.tasks
at this point always returned the current task (there was never an array of tasks). This perspective is from my own testing of configuring the workflow - nothing I sleuthed out in the code.
5576795
to
b68b029
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanation about freehand lines - I think it'll prevent confusion and bugs related to task type in the future. While addressing behavior of caesar reductions and the freehand line tool, I think we've skipped over an important test for the combo of multi_image_clone_markers
and frame
. See my comment in InteractionLayerContainer for details.
const visibleMarksPerFrame = (multiImageCloneMarkers) | ||
? newMarks | ||
: newMarks?.filter((mark) => mark.frame === frame) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By referencing multi_image_clone_markers
only in this one spot, we're not meeting the expectation described in your comment on the linked Issue:
...when we clone marks across frames the mark.frameIndex will always be 0 instead of whichever frame the mark was drawn or edited.
Here's an example describing the combo of multi_image_clone_markers
and frame
:
I created a test production workflow where the subjects are multi-frame and there are two steps. Each step is one point tool. https://localhost.zooniverse.org:3000/projects/goplayoutside3/fem-flipbook-viewer/classify/workflow/24066?env=production
Run the project locally on this branch, and when multi_image_clone_markers
is true, the mark I draw in the first step displays on all frames. I drew it on the first frame while testing. However, when I go to the next step, the mark I drew in the first step is no longer cloned and it only displays on the first frame. This is also true for the second step. For example if I'm on the second step, I draw a mark on 3rd frame and it displays as cloned, then go back to the first step, that mark is no longer cloned. It only shows up on the 3rd frame.
I believe this is happening because when multi_image_clone_markers
is true, there's no handling of mark.frame
for new marks in this PR. Try out the above test workflow by drawing marks on different frames, submit the classification and in the logged object, you'll see the annotations
array contains marks with frame
as the actual frame I clicked on. According to the comment above, new marks created when multi_image_clone_markers = true
should all have frame: 0
.
Accomplishing this might look something like interactionFrame = multiImageCloneMarkers ? 0 : frame
in InteractionLayerContainer, and then passing interactionFrame
to the InteractionLayer for its createMark()
function. I think you'd also need to pass interactionFrame
to PreviousMarks.
(feel free to toggle cloning config on my test workflow while in admin mode: https://www.zooniverse.org/lab/19645/workflows/24066).
@@ -11,29 +11,22 @@ const FreehandLineReductions = types | |||
workflowId: types.string | |||
}) | |||
.volatile(self => ({ | |||
isUsed: types.boolean, | |||
isUsed: types.array(types.boolean), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to my previous review - is this actually used anywhere? What is it for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
b68b029
to
83ed5f5
Compare
@kieftrav copying over your comment from Slack:
This description ^ is a recreation of the bug I described in this comment. It's fixed by using the solution in #5913.
In short, PreviousMarks component does not care about whether there's a config for cloning or not cloning. It cares only about For caesar reductions:It's okay that caesar marks aren’t registered as potential previous marks unless a new mark is created / existing mark is edited. This is not a bug. For instance, earlier in this PR discussion I mentioned that "in the transcription project Corresponding with Quakers, the caesar reductions only show on the first step." This is because the transcription caesar reductions are not previous marks made by the volunteer. It's fine to merge this PR without changing "caesar reductions as previous marks" behavior. I also don't want to unnecessarily speculate further about a project that has different caesar reductions in two different steps. That's not config we've supported yet, and the zoo-team can handle that special case when it arises. I opened #5913 to illustrate how to solve all of the above concerns. Please review + merge it into this PR, and then I'll approve this PR 👍 |
@goplayoutside3 - as discussed on #5913, the observed bug was unrelated to the changes made in that PR. I'm also going to copy over the reasoning about not changing the ==== Because modifying the frame is a data export issue decided in #5493 and is confined to the mark itself, I think it's important to keep the comment about the GitHub issue next to the area that sets its value to 0 instead of creating a chain of assumptions about why we changed the frame itself to 0 for the whole InteractionLayer when all that matters is that the classification export registers the mark's frame as 0 upon creation. I think this is particularly poignant because allowing the frame number to be set to its real frame when multiImageCloneMarkers is true doesn't change behavior at all - it still works as expected cloning all marks across all frames. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noting that #5919 is included here 👍
Responding to your comment on #5913 for context since that PR will be probably be closed:
I don't think all of the other changes here make sense - they're focused on changing the frame on the container element instead of focusing on the mark itself having frame=0.
This is intentional. InteractionLayerContainer's purpose is to grab MST values and pass them to its children. When multiImageCloneMarkers = true
, the frame number is coerced to 0
for all of InteractionLayerContainer's children. Setting frame = 0
in the parent of both InteractionLayer and PreviousMarks in #5913 is intentional.
Because modifying the frame is a data export issue decided in #5493 and is confined to the mark itself, I think it's important to keep the comment about the GitHub issue next to the area that sets its value to 0...
Yes agreed it's good to tag the decision in the linked Issue.
...instead of creating a chain of assumptions about why we changed the frame itself to 0 for the whole InteractionLayer when all that matters is that the classification export registers the mark's frame as 0 upon creation.
However, it's not just a data export decision. It matters that frame = 0
for PreviousMarks too. Nothing is exported from PreviousMarks.
I think this is particularly poignant because allowing the frame number to be set to its real frame when multiImageCloneMarkers is true doesn't change behavior at all - it still works as expected cloning all marks across all frames.
I don't think I understand this part - when would this scenario occur? When multiImageCloneMarkers
is true, the frame number is not the real frame. It's coerced to 0
for all of InteractionLayerContainer's children.
Because we resolved the issue with losing track of marks in the PreviousMarks component (i.e. Jim's fix) the current PR works as expected as far as I can tell.
Correct. Issues are resolved in this PR so I'm approving it. Feel free to close other related PRs as needed, or take the above comments into consideration.
@@ -216,5 +230,6 @@ InteractionLayer.propTypes = { | |||
width: PropTypes.number.isRequired | |||
} | |||
|
|||
export default InteractionLayer | |||
export default withStores(InteractionLayer, storeMapper) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think multiIimageCloneMarkers
should simply be passed to InteractionLayer from its container rather than hooking up the store in this file. InteractionLayerContainer's purpose is to grab MST values and pass them to its children, so it's not worth breaking that pattern in this PR.
Also withStores is an outdated method for Class components. New connections to the MST store should have useStores instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point on this. I pulled all of the code around this out and instead am passing the multiImageCloneMarkers
prop from the InteractionLayerContainer
to the InteractionLayer
.
Noted on useStores vs withStores going forward.
9f77c51
to
c93f21f
Compare
Package
Linked Issue and/or Talk Post
Issue #5493
Issue #5833 (unresolved in this PR)
Describe your changes
FlipbookViewer
,SeparateFrameViewer
, andSingleImageViewer
components.FlipbookViewer
hook from the
SubjectViewerStore` for all frame changesFlipbookViewer
spec given serial vs async test runningHow to Review
When deployed to project branch.
General
yarn panic && yarn bootstrap
ordocker-compose up --build
and FEM works as expected