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 primitive picking filters for Mesh and Markers visuals #2500
Conversation
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.
Awesome @aganders3! This is a lot smaller than I expected in terms of actual changes :) And I see how this can be adapted to other visuals!
One thing I noticed in the picking
mode builtin in the canvas is that you can actually render a specific region
and/or crop
of the canvas. Maybe we can use the same here to improve performance, by only rendering a small area around the cursor. Or am I misunderstanding?
Thanks! Regarding cropping - you're probably right but I have not tested! I can try this and do some profiling. I'll note right now this is pretty much left up to the downstream developer because I didn't add anything like |
Yep, I like tis approach, it can be used in many ways. We can handle the actually rendering/picking downstream. Would still be nice to see it in the example just to show it how works! |
As far as I can tell, this filter could be used almost as-is for |
I think I see what you mean - same with some of the other visuals. Would you like me to try to add that to this PR? I guess that would need a separate but similar filter (more or less) for each visual type? Or do you have an idea for how it could be a single class -- maybe it could dispatch |
It's actually slightly more complex, because here you also rely on
Yea, I think the easiest way would be to have a main class with the shader logic, and each subclass has the logic to go from
Not necessarily other visuals, but if you could set this up so it's extensible in a followup PR it would be great! On the other hand, this is already mergeable on its own, so feel free to choose where to put your energy :) |
Just dropping by to say 😍! |
Wait, I just noticed something in the initial video: when you display the picking, quads are colored rather than triangles. Something's off with indexing in that mode? |
It was not too bad to add marker picking. I'm going to clean it up and see if there's a good way to abstract the base class Screen.Recording.2023-06-15.at.11.49.48.AM.mov |
@brisvag this is now refactored into a base class and two picking filters for Markers and Mesh visuals. Please take another look when you have a chance! |
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.
Awesome! A few comments here and there, but nothing major. Supert excited to use this in napari, will be a huge speed up and improvement in rensponsiveness!
vispy/visuals/filters/base_filter.py
Outdated
self._id_colors = VertexBuffer(np.zeros((0, 4), dtype=np.float32)) | ||
vfunc['ids'] = self._id_colors | ||
self._n_primitives = 0 | ||
super().__init__(vcode=vfunc, fcode=ffunc) |
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.
Might be a good idea to set fpos
to a high value (like 1e9
or so), since we really want this color change to happen at the end of any other filter.
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 definitely a good idea. One question is how it should be set relative to the existing PickingFilter
, which only sets fpos=10
.
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 just played with this a bit and it's a small problem if fpos
is higher than that for PickingFilter
(10), then visual picking (i.e. visuals_at
) is broken. This doesn't feel like a common use case, but still.
My first instinct is to set fpos
on the existing PickingFilter
to something high like 1e9
and something like 1e9-1
below that on the PrimitivePickingFilter
ABC, but I think this would be a breaking change.
Otherwise maybe just set fpos
here to 9
but also it to __init__
in case that's not sufficient. (edit: this is in 6034cba)
What do you think?
@@ -120,3 +125,98 @@ def _detach(self, visual): | |||
|
|||
self._attached = False | |||
self._visual = None | |||
|
|||
|
|||
class PrimitivePickingFilter(Filter, metaclass=ABCMeta): |
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.
Any reason to use ABCMeta
other than just inherit from ABC
? Not really important, just curious.
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 I chose to specify the metaclass directly because it was already inheriting from another class so this felt more explicit. I was a little back-and-forth on this because it does the exact same thing and even seeing the word "metaclass" can be a bit ominous, haha.
vispy/visuals/filters/base_filter.py
Outdated
Generally, this method should be implemented to: | ||
1. Calculate the number of primitives in the visual, stored in | ||
`self._n_primitives`. | ||
2. Pack the picking IDs into a 4-component float array (RGBA | ||
VertexBuffer), stored in `self._id_colors`. |
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.
Would be awesome if the packing was done here instead of in the subclasses, since it's error prone and anyways it's always the same... Youy probbaly nee dto juggle some extra submethods calling each other, but it would be useful. not blocking though!
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 agree but didn't immediately have a way to do it that felt clean/intuitive so I punted and tried to compensate in the docstring. I'll give it a bit more thought because it does seem possible.
An alternative would be to move the packing into the shader and instead just pass the IDs in the VertexBuffer
. I avoided this for now just because I'm more comfortable with numpy than I am with GLSL but I don't think it would be too difficult.
Since these are also "just" implementation details I'm also happy to explore both options in a new PR if we want this to (potentially) be available a bit quicker.
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.
See 36db4fc for one idea
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 was thinking more something like:
@staticmethod
def _pack_ids_into_rgba(ids):
# pack ids
...
return packed
@abstractmethod
def _get_picking_ids(self):
# this is the method that subclasses will change, which simply
# calculates the ids and spits them out as normal numpy array
# and it could for example return None if no change is needed
# to improve performance
return None
def _update_id_colors(self):
# this should remain untouched
ids = self._get_picking_ids()
if ids is None:
return
id_colors = self._pack_ids_into_rgba(ids)
self._id_colors.set_data(id_colors)
def _on_data_updated(self, event=None):
if not self.attached:
return
self._update_id_colors()
A bit more complicated, but removes the need to think about packing from the sublcasses, and should make it easier to switch to a shader implementation later as well.
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, thanks. This looks good. I'll update.
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.
Since these are also "just" implementation details I'm also happy to explore both options in a new PR if we want this to (potentially) be available a bit quicker.
To be clear, perfectly happy with this as well!
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.
No worries, it was a pretty quick refactor. The latest commit (6022b98) has these changes plus the discard_transparent
property.
@@ -573,6 +574,8 @@ def __init__(self, scaling="fixed", alpha=1, antialias=1, spherical=False, | |||
blend_func=('src_alpha', 'one_minus_src_alpha')) | |||
self._draw_mode = 'points' | |||
|
|||
self.events.add(data_updated=Event) |
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.
Should we add this to the base visual to ensure it always exists? @djhoese
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 wouldn't be against that. I'd be a little worried that users seeing it would assume that it is properly emitted/handled by all visuals which won't be true after a lot of work. Updating every set_data
, right? Regardless, moving it to the base classes and documenting that it is new in version X and may not be used/emitted by all Visuals would be a good start.
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.
Yeah, definitely document it. But I guess it's also not too much work to simply emit this in every visual's set_data.
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 problem becomes the Visuals that have other properties for setting data like .color = new_color
and don't end up calling a .set_data
method at all.
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.
If it's okay I would just leave it as-is for now. I can look into moving this to the base visual (and updating visuals to emit it) in another PR.
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.
Sounds good to me!
Welcome back and thanks for the great comments! I have another question now that I have looked more closely at the existing if( gl_FragColor.a == 0.0 )
discard; |
This stuff should already be handled by the visuals themselves, imo, if required. If a visual does exist somewhere but is fully transparent, I can imagine cases where you'd still want to detect it with the picking mode... Maybe having a flag for it ( |
I think vispy's handling of transparent/invalid/discarded pixels is not consistent between Visuals. Some, like the ImageVisual's NaN handling, I think discard NaN floating point values from floating point textures. But I think the same Visual uses alpha=0 for 8-bit integer textures. The picking shaders are applied after the Visual's shader, right? So if a Visual's fragment shader did |
Exactly, which is why I think it's useful to be able to both include or exclude transparent fragments. |
Btw, some inspiration for doing this in the shader (here or later): https://stackoverflow.com/questions/28910861/opengl-unique-color-for-each-triangle |
I tried moving this into the shader, but I'm stuck unable to pas the ids as uint32 in the VertexBuffer:
I tried some workarounds, but I don't think you can do casting in glsl, and some possibly useful features ( |
also jumping in to say 😍 |
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, I think we're good to go! @djhoese, feel free to take a last look and/or merge!
Thanks again for the reviews. I was away all last week but am available now to address any remaining issues. |
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 downloaded the CI artifact (a zip of the rendered HTML) and yeah this looks good. Very cool. Thanks for figuring it out and for getting this all working.
# Description Just a simple bump to our vispy dependency, now that 0.14 is out. This comes with a few fixes and new features relevant for napari: - Fix for wrong spherical marker depth: vispy/vispy#2506 - Fix for wrong shading of surfaces with flipped normals: vispy/vispy#2493 - picking filters for mesh and markers by @aganders, which can now be used in napari to drastically speed up our get-status: vispy/vispy#2500 --------- Co-authored-by: Grzegorz Bokota <bokota+github@gmail.com> Co-authored-by: napari-bot <napari-bot@users.noreply.github.com>
Just a simple bump to our vispy dependency, now that 0.14 is out. This comes with a few fixes and new features relevant for napari: - Fix for wrong spherical marker depth: vispy/vispy#2506 - Fix for wrong shading of surfaces with flipped normals: vispy/vispy#2493 - picking filters for mesh and markers by @aganders, which can now be used in napari to drastically speed up our get-status: vispy/vispy#2500 --------- Co-authored-by: Grzegorz Bokota <bokota+github@gmail.com> Co-authored-by: napari-bot <napari-bot@users.noreply.github.com>
This adds a filter for picking individual faces from a mesh. It works similarly to the picking mode already in use for picking visuals but instead colors the individual faces (triangles) of the mesh based on an index.
I also added an example (heavily borrowing from the mesh shading example) to illustrate how the feature works and how to use it. In the example the mouse will "paint" faces as it moves over a mesh. Pressing 'p' will show the face picking view, that is it will show the colors used to encode the face indices.
Screen.Recording.2023-06-14.at.11.09.53.AM.mov