Conversation
* Add some notes on shapes layer state. * Update docs/naps/4-async-slicing.md Co-authored-by: Andy Sweet <andrew.d.sweet@gmail.com> * Update docs/naps/4-async-slicing.md Co-authored-by: Andy Sweet <andrew.d.sweet@gmail.com> * Update docs/naps/4-async-slicing.md Co-authored-by: Andy Sweet <andrew.d.sweet@gmail.com> * Update docs/naps/4-async-slicing.md Co-authored-by: Andy Sweet <andrew.d.sweet@gmail.com> * Update docs/naps/4-async-slicing.md Co-authored-by: Andy Sweet <andrew.d.sweet@gmail.com> * Cleanup of notes on Shapes layer. Co-authored-by: Andy Sweet <andrew.d.sweet@gmail.com>
* add_vector_info * Apply suggestions from code review Co-authored-by: Andy Sweet <andrew.d.sweet@gmail.com> * add author Co-authored-by: Kim Pevey <kpevey@quansight.com> Co-authored-by: Andy Sweet <andrew.d.sweet@gmail.com>
This reverts commit e92077c.
Co-authored-by: Nicholas Sofroniew <sofroniewn@gmail.com>
Czaki
left a comment
There was a problem hiding this comment.
I like this. I will go back after some time after a rethink.
| The main goal of this project is to perform slicing asynchronously, so it's | ||
| natural that we might break anyone that was depending on slicing being synchronous. | ||
| At a minimum, we must provide a public way to achieve the same fundamental goals, | ||
| such as connecting to the `slice_ready` event. |
There was a problem hiding this comment.
Writing a project that uses napari to create animation having only slice_ready will be much harder than for the current state as it will require a switch from some form of only loop to object keeping state to pass around waiting for the event. It may be nice to have fully sync mode.
@alisterburt I think that it may touch your work.
There was a problem hiding this comment.
napari animation is a great call out, we'd have to make that respond to some event after rendering is done to make sure we captured the screenshot at the right time. we might need an additional event then or a force sync mode
There was a problem hiding this comment.
I think you mentioned this verbally, but thanks for writing it down. I think it would be a useful exercise to try to see how hard it is to make napari-animation work with the current prototype in: andy-sweet#21
I'm planning to do that, but won't complain if someone else gets to it before me!
There was a problem hiding this comment.
Absolutely incredible work @andy-sweet
I'm approving this - I think it should be merged ASAP and discussion should follow in a subsequent PR
| The main goal of this project is to perform slicing asynchronously, so it's | ||
| natural that we might break anyone that was depending on slicing being synchronous. | ||
| At a minimum, we must provide a public way to achieve the same fundamental goals, | ||
| such as connecting to the `slice_ready` event. |
brisvag
left a comment
There was a problem hiding this comment.
Amazing work @andy-sweet et Al.! :D This is super thorough, I'm sure it will be a breeze to implement with all these details already ironed out ;)
I have a few comments below, but as @alisterburt said, we should merge this ASAP, so feel free to ignore them for now and address them in a second PR.
| - `_mesh_vertices`, output from `generate_vector_meshes`. | ||
| - `_mesh_triangles`, output from `generate_vector_meshes`. |
There was a problem hiding this comment.
Note that this is getting moved to the vispy side in #4846.
| - The implementation details for some layer/data types might be complex (e.g. multi-scale image), but the high level logic should be simple. | ||
| - P1. Move the slice state off the layer, so that its attributes only represent the whole data. | ||
| - Layer may still have a function to get a slice. | ||
| - May need alternatives to access currently private state, though doesn't necessarily need to be in the Layer (e.g. a plugin with an ND layer, that gets interaction data from 3D visualization , needs some way to get that data back to ND). |
There was a problem hiding this comment.
Can you clarify what you mean byt this? That a LayerSliceResponse should know where it lives in the world coordinates?
There was a problem hiding this comment.
Yes, I think that's right. I believe this originally came up in a discussion with @alisterburt.
The response knows where it lives in the scene (i.e. the subspace of the world that is being visualized), but slicing is a lossy operation that means the output loses the info to get back to ND world coordinate system.
I guess if the response also stores at least some parts of the request (e.g. the bounding box that defines which region which are slicing), then that might be enough because the non-visualized dimensions are the sliced dimensions (either at an exact coordinate or spanning a range like thick slices).
| At this point `data` should be fast to access either from RAM (e.g. as a numpy | ||
| array) or VRAM (e.g. as a CuPy array), so that it can be consumed on the main | ||
| thread without blocking other operations for long. |
There was a problem hiding this comment.
I think this requires some extra thought; I'm trying to imagine how this all would work out in a possible future where we have DataSources that maintain data loaded onto the GPU so that we can for example share visualizations between canvases, or between layers, or allow faster slicing in the shader. Indeed, I'm working towards this in PRs such as vispy/vispy#2350 and vispy/vispy#2351. Is there a way we can take advantage of this async machinery, while also allowing "pre-loading" data onto the GPU and do the slicing in the shader?
There was a problem hiding this comment.
Slicing in the shader is super interesting. I suspect that will need to be in addition and complementary to this PR, so you could say have a 4D timeseries dataset that you sliced to get a smaller single 3D timepoint that you "pre-loaded" (as you say above) and then sliced in the GPU to get a specific 2D plane. Does it sound like we're on the same page here @brisvag?
There was a problem hiding this comment.
Yup :) But I think the biggest advantage would be for multicanvas in order to display the same data multiple times using the same texture; imagine having a sizeable 3D volume on which you performed feature picking in 10 different ways, and you'd like to see them side by side. You could have a grid of 10 canvases with linked cameras and zoom in onto details and compare, but without sharing texture you'd be decuplicating the vram usage.
There was a problem hiding this comment.
This might be an oversimplification, but as long as the thing that's rendering the slice (e.g. vispy) has enough to be able to do that "quickly", then I think the job of slicing as described here is done. In the case described in the comments above (i.e. 4D data, same underlying 3D slice with different features presented in multiple canvases), I'd imagine that slicing just wouldn't do much after the 3D sliced data has been read for the first time. Not quite sure what data would be in that case, which is one motivation to keep the typing of that as vague as possible for now (e.g. Any) and maybe keep things temporarily private.
| In this proposal, the slicing thread waits for slices of all layers to be ready before | ||
| it emits the `slice_ready` event. There are a few reasons for that. |
There was a problem hiding this comment.
I had another comment about this, but I see now you addressed it already. I think this would be a big improvement, especially because some slicing operations are CPU-intensive (Points) rather than I/O. But if it makes it easier to keep this for a later data, that's ok!
There was a problem hiding this comment.
Points slicing could be seedup (by the cost of memory), outside this NAP as we use suboptimal way of storage
There was a problem hiding this comment.
Yes, definitely! some thought already went into this from @kevinyamauchi :)
| - Should `Dims.current_step` represent the last slice position request or the last slice response? | ||
| - With sync slicing, there is no distinction. | ||
| - If it represents the last slice response, then what should we connect the sliders to? | ||
| - Similar question for `corner_pixels` and others. |
There was a problem hiding this comment.
I think it should be the last request. Maybe a way of preventing issues is to have a temporary NotLoaded sentinel value in places where things are out of date and users might access them. Or introduce a lock so that if one tries to access anything out-of-sync, they'll have to wait.
There was a problem hiding this comment.
There seems to be some general consensus around last request. Some other related comments about this are that we consider storing the last response equivalent of this and other similar state, though maybe not publicly.
|
|
||
| - Should we maintain the synchronous behavior of `refresh` and `set_view_slice`? | ||
|
|
||
| - Should we invert the current design and submit async tasks within each vispy layer? |
There was a problem hiding this comment.
This would prevent us from easily re-using this logic with another backend in the future.
There was a problem hiding this comment.
This would prevent us from easily re-using this logic with another backend in the future.
Not necessarily. Most of the slicing logic proposed here is defined in the layers (in particular Layer._get_slice) and in LayerSlicer and this idea wouldn't change that. I haven't done a great job of explaining what the alternative is, so I'll probably update this text with some pseudocode and list it as an alternative considered.
sofroniewn
left a comment
There was a problem hiding this comment.
Looking great - I added a couple of my most major comments. Happy to have this merge and discussion continue elsewhere too
| point: Tuple[float, ...] | ||
| dims_displayed: Tuple[int, ...] | ||
| dims_not_displayed: Tuple[int, ...] |
There was a problem hiding this comment.
I wonder if we should create a dedicated Slice object that includes all the includes all the encapsulated dims and camera info to determine what part of the data to show. Slice could then be an additional argument to a slicing function. Thoughts @jni - I know you don't want a proliferation of classes, but to me that seems like a helpful one
There was a problem hiding this comment.
Yeah, I like this idea and I think @nclack has a similar one: #4892 (comment)
Would be great to fold a lot of base layer's request state into a world bounding box.
|
|
||
| Second, we introduce a response to encapsulate the output of slicing. | ||
|
|
||
| ```python |
There was a problem hiding this comment.
Could we not have one LayerSlice object instead of both LayerSliceRequest and LayerSliceResponse? Especially if we have encapsulated all the dims/ camera info into a single Slice object then I think all the other attributes of the two objects are the same - though I guess there could be times when they are not.
I'm trying to think from the perspective of someone adding a new layer and trying to make sure that the number of new objects added is as small as possible - ideally two, one for the full layer and then one for the slice data.
I tried exploring something like that here with the datasource idea from @d-v-b and quite liked it's feel https://github.com/napari/napari/pull/1908/files#diff-22adcfbe4884971c3fdb13eae4a24a4683d3365fb437b02df0e9f147f2c854dbR8-R29 I called it PointsData and had a "full" version of it actually live on the layer too.
Curious what @jni @tlambert03 @nclack think
There was a problem hiding this comment.
Something just feels a bit off to me with having the request & response in a single object. I'd like both objects to be immutable, but I don't have a strong case for it.
Separately, what would it look like to to send LayerSliceResponse to something other than vispy visual, e.g., a web-based widget?
There was a problem hiding this comment.
Separately, what would it look like to to send LayerSliceResponse to something other than vispy visual, e.g., a web-based widget?
That's a great question! Would be awesome to try as a proof of concept
Something just feels a bit off to me with having the request & response in a single object. I'd like both objects to be immutable, but I don't have a strong case for it.
That's fair - I just want to minimize burden on folks contributing new layers. I could be they just have to add one LayerDataSource object and we turn it into request and response
There was a problem hiding this comment.
Definitely want to avoid any kind of class explosion, but I also don't love the idea of defining one type for two different purposes. I wonder if frozen dataclasses are less of a concern than more complex classes (i.e. with methods that have side effects). I'd also be curious to know if unpacking the request into keyword arguments for Layer.get_slice is another option that might make things easier to understand.
There was a problem hiding this comment.
I'd also be curious to know if unpacking the request into keyword arguments for Layer.get_slice is another option that might make things easier to understand.
i actually liked this less, to me it became too many kwargs
I also don't love the idea of defining one type for two different purposes
ok all this is fair - we can start with two classes - one request & one response. always possible to refactor later and at least being explicit has advantage of being clear
I still like the idea of a dedicated Slice class that contains all the Camera & Dims info needed to make a slice.
| In the future, the point may become a bounding box that includes the current | ||
| field of view. |
There was a problem hiding this comment.
I like bbox much better. point could just get encoded in the (affine) transform, and the extent information from bbox would be very valuable.
I guess the proposed design is there because that fits the current use, hence "in the future". But imo the future is now :)
There was a problem hiding this comment.
I would love to collapse a few of the existing request fields into something bounding_box_world. The main reason I didn't do that yet is laziness - partly because some of the existing slicing code uses point by name and type. But I agree with you that it is probably the right time to make the switch, so I think this would be a great idea when we start writing code to be merged into napari.
There was a problem hiding this comment.
One thing that makes me hesitant is around dimension correspondence. Ideally, the bounding box would have named dimensions and we could do the correspondence between its and a layer's dimensions by name. I think we should still be able to work with the numpy broadcasting correspondence we have now, but I haven't thought that through yet.
tlambert03
left a comment
There was a problem hiding this comment.
classic Andy here :) Just a beautiful document and a great, clear proposal. I'm all for merging this whenever. I'd like a little more time to digest the specifics in the proposal, but after a first pass, I didn't see anything I didn't like. Thanks @andy-sweet!
|
Given the consensus here around merging the NAP sooner rather than later, I will go ahead and merge this. We can continue discussion in a variety of forums though. Great work @andy-sweet on submitting your first NAP 🚀 |
Description
This opens NAP 4, which proposes to make slicing in napari asynchronous. Thanks to for @junxini, @perlman, and @kcpevey for helping to write this! And others for any preliminary feedback!
I should have probably opened this PR before the document grew quite so large, so apologies for that! Some people may have seen the Motivation and scope section before (which hasn't changed much at all in a few months), but now there is a description of how slicing currently works in napari in Related work and an actual technical proposal (in Detailed description) about how to achieve some of those goals.
I think the proposal here is coherent, but it is not intended to be complete or final. I definitely expect some feedback, criticism, and alternative ideas. Happy to incorporate those before or after this gets merged.
I added some diagrams, so it's probably easiest to read the rendered document on this PR: https://github.com/andy-sweet/napari/blob/55b025a6013f90914f15bca7030aca98ed542b40/docs/naps/4-async-slicing.md
If you want to see what some of the code may look like, how this works, and how/where it breaks, check out the prototype PR: andy-sweet#21
References
Tracking issue: #4795