10000 Speed up `get_status` for Shapes layer by using bounding boxes by Czaki · Pull Request #7144 · napari/napari · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Speed up get_status for Shapes layer by using bounding boxes #7144

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

Merged
merged 27 commits into from
Sep 26, 2024

Conversation

Czaki
Copy link
Collaborator
@Czaki Czaki commented Jul 30, 2024

References and relevant issues

Description

When investigating why napari interface is so lagging with big number of layers, I have spotted that Shapes triangulation is used to determine if the mouse is inside some shape. However, a single shape could be constructed from multiple polygons. Also, edge polygons are used.

This PR adds information about bounding box to Shape object. Then filtering of Shapes based on belonging cursor position to bounding box of shape.

Then checking if the cursor is in such a filtered list of shapes is performed based on triangles (same as previous).

@Czaki Czaki added the run-benchmarks Add this label to trigger a full benchmark run in a PR label Jul 30, 2024
@Czaki Czaki added run-benchmarks Add this label to trigger a full benchmark run in a PR and removed run-benchmarks Add this label to trigger a full benchmark run in a PR labels Jul 30, 2024
Copy link
Contributor

The Qt benchmark run requested by PR #7144 (153f835 vs 93140b9) has finished with status 'failure'. See the CI logs and artifacts for further details.
The non-Qt benchmark run requested by PR #7144 (153f835 vs 93140b9) has finished with status 'failure'. See the CI logs and artifacts for further details.

@github-actions github-actions bot removed the run-benchmarks Add this label to trigger a full benchmark run in a PR label Jul 31, 2024
Copy link
Contributor

The Qt benchmark run requested by PR #7144 (d8d4f32 vs 93140b9) has finished with status 'failure'. See the CI logs and artifacts for further details.
The non-Qt benchmark run requested by PR #7144 (d8d4f32 vs 93140b9) has finished with status 'failure'. See the CI logs and artifacts for further details.

Copy link
codecov bot commented Jul 31, 2024

Codecov Report

Attention: Patch coverage is 98.55072% with 1 line in your changes missing coverage. Please review.

Project coverage is 92.73%. Comparing base (6a72ab5) to head (079ef7e).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
napari/layers/shapes/_shape_list.py 95.65% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7144      +/-   ##
==========================================
- Coverage   92.81%   92.73%   -0.09%     
==========================================
  Files         623      623              
  Lines       57347    57400      +53     
==========================================
+ Hits        53227    53228       +1     
- Misses       4120     4172      +52     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@Czaki Czaki added the run-benchmarks Add this label to trigger a full benchmark run in a PR label Jul 31, 2024
Copy link
Contributor

The Qt benchmark run requested by PR #7144 (cecb267 vs 93140b9) has finished with status 'failure'. See the CI logs and artifacts for further details.
The non-Qt benchmark run requested by PR #7144 (cecb267 vs 93140b9) has finished with status 'success'. See the CI logs and artifacts for further details.

@github-actions github-actions bot removed the run-benchmarks Add this label to trigger a full benchmark run in a PR label Jul 31, 2024
@Czaki Czaki added the run-benchmarks Add this label to trigger a full benchmark run in a PR label Aug 1, 2024
@github-actions github-actions bot added tests Something related to our tests qt Relates to qt labels Aug 1, 2024
@Czaki Czaki removed the run-benchmarks Add this label to trigger a full benchmark run in a PR label Aug 1, 2024
Copy link
Contributor
github-actions bot commented Aug 1, 2024

The Qt benchmark run requested by PR #7144 (83b61b7 vs 93140b9) has finished with status 'failure'. See the CI logs and artifacts for further details.
The non-Qt benchmark run requested by PR #7144 (83b61b7 vs 93140b9) has finished with status 'failure'. See the CI logs and artifacts for further details.

@Czaki Czaki added the run-benchmarks Add this label to trigger a full benchmark run in a PR label Aug 1, 2024
Copy link
Contributor
github-actions bot commented Sep 2, 2024

The Qt benchmark run requested by PR #7144 (abf86c7 vs 35a9936) has finished with status 'failure'. See the CI logs and artifacts for further details.

Change Before [35a9936] <v0.5.3rc0~11> After [abf86c7] Ratio Benchmark (Parameter)
+ 11.8±1ms 36.5±6ms 3.09 benchmark_qt_slicing.QtViewerAsyncImage2DSuite.time_z_scroll(0.1, 'jrc_hela-2 (scale 3)')
+ 11.6±2ms 28.7±10ms 2.47 benchmark_qt_slicing.QtViewerAsyncImage2DSuite.time_z_scroll(0.0, 'jrc_hela-2 (scale 3)')
+ 426±2μs 826±200μs 1.94 benchmark_qt_slicing.AsyncImage2DSuite.time_refresh(0.0, 'skin_data')
+ 2.02±0.3ms 3.76±0.3ms 1.86 benchmark_qt_viewer_image.QtViewerImageSuite.time_refresh(64)
+ 2.59±0.5ms 4.02±3ms 1.55 benchmark_qt_viewer_image.QtViewerSingleImageSuite.time_set_data

SOME BENCHMARKS HAVE CHANGED SIGNIFICANTLY.
The non-Qt benchmark run requested by PR #7144 (abf86c7 vs 35a9936) has finished with status 'success'. See the CI logs and artifacts for further details.

Change Before [35a9936] <v0.5.3rc0~11> After [abf86c7] Ratio Benchmark (Parameter)
- 17.9±0.3ms 10.7±0.04ms 0.6 benchmark_shapes_layer.Shapes2DSuite.time_get_value(64)
- 26.7±0.3μs 15.4±0.09μs 0.58 benchmark_shapes_layer.Shapes3DSuite.time_get_value(64)
- 26.6±0.09μs 14.1±0.06μs 0.53 benchmark_shapes_layer.Shapes3DSuite.time_get_value(32)
- 26.5±0.1μs 13.1±0.02μs 0.49 benchmark_shapes_layer.Shapes3DSuite.time_get_value(16)
- 33.4±0.2ms 11.4±0.05ms 0.34 benchmark_shapes_layer.Shapes2DSuite.time_get_value(128)
- 68.3±0.2ms 12.4±0.06ms 0.18 benchmark_shapes_layer.Shapes2DSuite.time_get_value(256)

SOME BENCHMARKS HAVE CHANGED SIGNIFICANTLY.

@github-actions github-actions bot removed the run-benchmarks Add this label to trigger a full benchmark run in a PR label Sep 2, 2024
@@ -1018,6 +1020,7 @@ def transform(self, index, transform):
self.remove(index, renumber=False)
self.add(shape, shape_index=index)
self._update_z_order()
self.__dict__.pop('_bounding_boxes', None)
Copy link
Member

Choose a reason for hiding this comment

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

I had to infer here that cached_property stores the cache in self.__dict__, and this line is clearing the cache. I think it would be good to abstract this as a one-line private method, self._clear_bounding_box_cache()?

And (optional) rather than manually calling this in each method that changes the data, should we not just connect it to an event that fires any time the data changes?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It is not an evented model, and we already have performance problems with it.

(bounding_boxes[0] <= coord) * (bounding_boxes[1] >= coord),
axis=1,
)
inside_indices = np.nonzero(inside)[0]
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
inside_indices = np.nonzero(inside)[0]
inside_indices = np.flatnonzero(inside)

axis=1,
)
inside_indices = np.nonzero(inside)[0]
if not inside_indices.size:
Copy link
Member

Choose a reason for hiding this comment

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

For naturally integer values I think it is more readable to compare rather than to check for truthiness.

Suggested change
if not inside_indices.size:
if inside_indices.size == 0:

z_list = self._z_order.tolist()
order_indices = np.array([z_list.index(m) for m in shapes])
ordered_shapes = shapes[np.argsort(order_indices)]
return ordered_shapes[0]
Copy link
Member

Choose a reason for hiding this comment

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

Looking at this deleted code, I notice that you no longer sort the shapes by z_order. Are we sure that doesn't matter? (ie are the shapes in z_order anyway?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I do not understand this part of the code. It looks obsolete now, to me. It comes from #100 PR

if not self.shapes:
return None
bounding_boxes = self._bounding_boxes
inside = np.all(
Copy link
Member

Choose a reason for hiding this comment

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

I think this should be renamed "maybe_inside" or "in_bbox", reserving "inside" for shapes that actually contain the coordinate.

@property
def bounding_box(self) -> np.ndarray:
"""np.ndarray: 9x2 array of vertices of the interaction box."""
return self._bounding_box[:, self.dims_displayed]
Copy link
Member

Choose a reason for hiding this comment

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

the docstring doesn't seem to match the code?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

updated

@jni jni added the ready to merge Last chance for comments! Will be merged in ~24h label Sep 25, 2024
@jni
Copy link
Member
jni commented Sep 25, 2024

@Czaki before merging, could you double check that this PR works with modified z-order? By default, shapes added later are on top of shapes added first. But you can change the z-ordering of shapes by clicking the "move to front" and "move to back" buttons. We should compute intersections based on the z-order of shapes, not the order in which they were added. See this video:

shp-zorder.mp4

@Czaki
Copy link
Collaborator Author
Czaki commented Sep 25, 2024

@jni
Ok. I finally understand this z_order
I have fixed it. Worklike on video now.

@jni
Copy link
Member
jni commented Sep 26, 2024

Awesome, thanks! 🚀

@jni jni changed the title Speedup get_status for Shapes layer by use bounding boxes Speed up get_status for Shapes layer by using bounding boxes Sep 26, 2024
@jni jni merged commit d6f4087 into napari:main Sep 26, 2024
36 checks passed
@jni jni deleted the improve_status_shapes branch September 26, 2024 05:23
@github-actions github-actions bot removed the ready to merge Last chance for comments! Will be merged in ~24h label Sep 26, 2024
@jni jni added performance Relates to performance highlight PR that should be mentioned in next release notes ready to merge Last chance for comments! Will be merged in ~24h and removed ready to merge Last chance for comments! Will be merged in ~24h labels Sep 26, 2024
Czaki added a commit that referenced this pull request Oct 21, 2024
…7332)

# References and relevant issues


https://napari.zulipchat.com/#narrow/stream/212875-general/topic/Issue.20grabbing.20lines.20in.20shape.20layer/near/477001240

# Description

In #7144 we attempted to speed up mouse click testing in shapes by using
shapes bounding boxes. However, there were two bugs in that
implementation:

- it did not account for edge width around shapes, so one could only
click on the interior of a shape — which is hard for edge-only shapes
like paths 😅
- it did not account for certain classes of shapes data changes, so that
the bounding box data was out of date when shapes were transformed, and
clicking on the shape became impossible 🫣

This PR fixes both issues by:
- supplementing the bounding box by 0.5 times the edge width.
- Add missing cache clears.

It also adds tests for these missed elements.

---------

Co-authored-by: Carol Willing <carolcode@willingconsulting.com>
jni pushed a commit that referenced this pull request Dec 22, 2024
# References and relevant issues

closes #7458  

# Description

This PR closes bug with selecting shapes only from current slice,
introduced in #7144.

The source of the bug is not filtering out-of-slice shapes when
searching for intersecting triangles.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement highlight PR that should be mentioned in next release notes performance Relates to performance qt Relates to qt tests Something related to our tests
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
0