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
Add view ray and labels selection in 3D #3037
Conversation
❤️ ❤️ ❤️ view_ray.mp4 |
Super cool, @alisterburt . If others want to try it, see the @volume_layer.mouse_drag_callbacks.append
def on_click(layer, event):
near_point, far_point = layer._cursor_ray(event)
ray_points = np.linspace(near_point, far_point, n_points, endpoint=True)
if ray_points.shape[1] != 0:
ray_layer.data = ray_points |
Codecov Report
@@ Coverage Diff @@
## master #3037 +/- ##
==========================================
+ Coverage 82.69% 82.72% +0.03%
==========================================
Files 509 510 +1
Lines 42586 42912 +326
==========================================
+ Hits 35216 35500 +284
- Misses 7370 7412 +42
Continue to review full report at Codecov.
|
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.
@kevinyamauchi @alisterburt this is sooooo amazing!! I love it, I've given a bit of preliminary review now, I know it's a WIP so I hope you don't mind. I was mainly high level, a couple ideas around API/ naming etc, but all in all this is looking great and will be so much fun to play with!! Keep up the great work 🚀
napari/layers/base/base.py
Outdated
@@ -999,6 +1009,131 @@ def _data_to_world(self) -> Affine: | |||
""" | |||
return self._transforms[1:3].simplified | |||
|
|||
def vector_world_to_data(self, vector) -> tuple: |
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.
Maybe we use the word ray instead of vector? while i agree vector is reasonable, it also might make folks think of the vector layer, and I think ray is quite nice. We could use it everywhere.
Also how about making this method private, say _world_to_data_ray
, and then adding a kwarg to our existing method
def world_to_data(position, ray=False):
I know @jni might be a bit uneasy about a kwarg changing the meaning of the input, so maybe i should be overruled here, but just trying to think about the best naming. As noted in another comment below, you could do the same thing for get_value(position, ray=True)
which would be neat!
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.
Hmm I'm a bit torn on this one. My current leaning is towards having a separate method (we can choose a different name) for both transforming orientations (vector_world_to_data()
) and getting a value from a click since I think they are doing different things. For example, for get_value
, in 2D, the click point really is the point in the data the user intends to interrogate. Whereas, in 3D, the click position is actually a position in a plane unknown to the user in the data and there are different ways we can interrogate the data using that point.
edit: I think this may be the same thing as
I know @jni might be a bit uneasy about a kwarg changing the meaning of the input
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.
Ok, let's see what @jni thinks, but if we stick with two methods i might prefer world_to_day_ray
as a name and using ray
as our word throughout
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 might be misunderstanding, but I'm actually kinda leaning towards unifying the API. It feels like the same principle — get a 2D point in canvas, then depending on ndisplay
, this point implies either a 2D point in the data, or a 3D ray in the data.
In other words, we can use not a keyword argument like ray=True
, but the dimensionality of ray
/display as the switch.
btw the docstring below seems out of date, as dims_displayed
is not an argument of this method...
napari/layers/labels/labels.py
Outdated
@@ -893,6 +894,26 @@ def get_color(self, label): | |||
col = self.colormap.map(val)[0] | |||
return col | |||
|
|||
def _get_value_ray(self, start_point, end_point): |
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.
Is this going to turn into an abstract method on the base class that every layer will have to implement, sort of like some of the _get_value
? That makes a lot of sense to me.
If you liked the idea of adding a kwarg world_to_data
that I mentioned you could do the same thing with
get_value(self, position, *, world=False, ray=False):
which I think would be quite nice. cc @jni
Thank you for the feedback , @jni and @sofroniewn ! I think we are pretty much there in terms of the core code and we now just need to decide how to hook it up. Just to make sure we're all on the same page, a quick summary:
In terms of what I think users/developers will want to interact with:
To achieve this, I propose the following:
I think this is pretty in line with what @sofroniewn and @jni suggested, but I wanted to make sure we are all on the same page because I think we are going to want to build a lot on top of the functionality. What do you all think? |
All this sounds pretty good
I'm a tad nervous about this as it's somewhat funny to me that the mouse now knows about some of this dims stuff, but on the other hand I can see why it's needed and I don't really see another way right now.
Do you want to make a small dedicated PR that makes just these changes (without I saw @kevinyamauchi you've got some good tests inside the labels layer where you mock the mouse event. Can you make a copy of those tests that test the same stuff but put them at the level of the whole viewer and trigger the mouse event manually - I want to make sure that if we changed something like the addition of the |
Thanks for the feedback, @sofroniewn !
I agree it's a little funny. I also don't see another way at the moment and I don't think bad, as it's not part of the
Good call. Will do!
I think this is a great idea! This PR is definitely getting a little bulky. In that case, the last things are:
|
I guess that PR could also come after this one too. Happy to do it either way
How about |
Some minor comments on the discussion above:
But, whatever you decide @kevinyamauchi, let's keep it private still, so we can refactor to our heart's content after merge. 😊
Why not update the single Cursor model with Other than that, in general terms, I absolutely love the proposal to make methods less dependent on global state. |
btw @kevinyamauchi feel free to ping me on Zulip this morning if you work on this and want to bounce ideas off someone. I don't have a pair session scheduled tonight. 😊 |
Yeah that could be nice. Might want to keep the additional cursor properties private until we figure this out too, but we can always deprecate if we change |
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.
Nice work here @kevinyamauchi! Let's get this in!! 🚀
I'm approving now, but will probably let sit one more night in case anyone else wants to review/ has additional feedback - otherwise will plan on merging tomorrow morning. How does that sound?
@jni and I discussed this on zulip, but I'll just summarize here in case anybody is curious. It is challenging to put the front/back intersections on the After @tlambert03's and @sofroniewn 's feedback and some conversations with @jni and @alisterburt , I have made the following changes:
With all of that, this PR gives the ability to:
In the follow up PRs, we will:
I think this is pretty much ready to go! Looking forward to any final feedback you all might have! |
Sounds good to me! Thanks everyone! |
Do you want to use napari/napari/components/layerlist.py Lines 236 to 244 in 529181d
No need to use that now, but if that's useful and you want to change in this PR let me know - especially if it would change any public API that we're about to create |
@sofroniewn , cool! I didn’t know about that. It doesn’t affect anything in this PR, but it may inform how we hook the cursor model in the follow up PR. Thanks! |
@kevinyamauchi @sofroniewn note that we've had lots of performance issues with |
Ah yes - good point! |
Would be worth knowing though it if would be highly useful here though, and the we should cache layer extents - rather than just reimplementing the same thing in parallel |
Indeed, we should not reimplement. But I think what would be useful here is |
Description
In this PR, @alisterburt and I add the ability to select objects in 3D. Following the pattern suggested by @sofroniewn here, we:
layer. _map_canvas2world
to correctly map the canvas coordinates to world coordinates for 3D data. Previously, this method used the vispy camera transform. However, I think this may not be the correct transform between canvas and world coordinates for 3D, but based on my experimentation and this comment.layer._process_mouse_event()
To show the implementation, we have a example script with a layer that has rotation, shear, and scale applied (see below)
Example code
label_selection.mov
Some things to improve:
currently, the algorithm we use to construct the ray passing through the data cube requires the transform mapping canvase coordinates to world coordinates. Currently, this is not available in the layer and it would be nice if it was because it makes it difficult to do interaction in 3D.Fixed, see hereType of change
References
Closes #2653
How has this been tested?
Final checklist:
trans.
to make them localizable.For more information see our translations guide.