Skip to content

Commit 46ccae3

Browse files
committed
Implement PolygonSelector
1 parent 124d79e commit 46ccae3

File tree

9 files changed

+280
-77
lines changed

9 files changed

+280
-77
lines changed

docs/source/api/graphics/ImageGraphic.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Methods
4848
ImageGraphic.add_linear_region_selector
4949
ImageGraphic.add_linear_selector
5050
ImageGraphic.add_rectangle_selector
51+
ImageGraphic.add_polygon_selector
5152
ImageGraphic.clear_event_handlers
5253
ImageGraphic.remove_event_handler
5354
ImageGraphic.reset_vmin_vmax

docs/source/api/graphics/LineCollection.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ Methods
5353
LineCollection.add_linear_region_selector
5454
LineCollection.add_linear_selector
5555
LineCollection.add_rectangle_selector
56+
LineCollection.add_polygon_selector
5657
LineCollection.clear_event_handlers
5758
LineCollection.remove_event_handler
5859
LineCollection.remove_graphic

docs/source/api/graphics/LineGraphic.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ Methods
4747
LineGraphic.add_linear_region_selector
4848
LineGraphic.add_linear_selector
4949
LineGraphic.add_rectangle_selector
50+
LineGraphic.add_polygon_selector
5051
LineGraphic.clear_event_handlers
5152
LineGraphic.remove_event_handler
5253
LineGraphic.rotate

docs/source/api/graphics/LineStack.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ Methods
5353
LineStack.add_linear_region_selector
5454
LineStack.add_linear_selector
5555
LineStack.add_rectangle_selector
56+
LineStack.add_polygon_selector
5657
LineStack.clear_event_handlers
5758
LineStack.remove_event_handler
5859
LineStack.remove_graphic

fastplotlib/graphics/features/_selection_features.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Sequence
22

33
import numpy as np
4+
import pygfx as gfx
45

56
from ...utils import mesh_masks
67
from ._base import GraphicFeature, GraphicFeatureEvent, block_reentrance
@@ -340,3 +341,96 @@ def set_value(self, selector, value: Sequence[float]):
340341

341342
# calls any events
342343
self._call_event_handlers(event)
344+
345+
346+
class PolygonSelectionFeature(GraphicFeature):
347+
event_info_spec = [
348+
{
349+
"dict key": "value",
350+
"type": "np.ndarray",
351+
"description": "new array of points that represents the polygon selection",
352+
},
353+
]
354+
355+
event_extra_attrs = [
356+
{
357+
"attribute": "get_selected_indices",
358+
"type": "callable",
359+
"description": "returns indices under the selector",
360+
},
361+
{
362+
"attribute": "get_selected_data",
363+
"type": "callable",
364+
"description": "returns data under the selector",
365+
},
366+
]
367+
368+
def __init__(
369+
self,
370+
value: Sequence[tuple[float]],
371+
limits: tuple[float, float, float, float],
372+
):
373+
super().__init__()
374+
375+
self._limits = limits
376+
self._value = np.asarray(value).reshape(-1, 3).astype(float)
377+
378+
@property
379+
def value(self) -> np.ndarray[float]:
380+
"""
381+
The array of the polygon, in data space
382+
"""
383+
return self._value
384+
385+
@block_reentrance
386+
def set_value(self, selector, value: Sequence[tuple[float]]):
387+
"""
388+
Set the selection of the rectangle selector.
389+
390+
Parameters
391+
----------
392+
selector: PolygonSelector
393+
394+
value: array
395+
new values (3D points) of the selection
396+
"""
397+
398+
value = np.asarray(value, dtype=np.float32)
399+
400+
if not value.shape[1] == 3:
401+
raise TypeError(
402+
"Selection must be an array, tuple, list, or sequence of the shape Nx3."
403+
)
404+
405+
# # clip values if they are beyond the limits
406+
# value[:, 0] = value[:2].clip(self._limits[0], self._limits[1])
407+
# # clip y
408+
# value[:, 1] = value[2:].clip(self._limits[2], self._limits[3])
409+
410+
self._value = value
411+
412+
# TODO: Update the fill mesh
413+
# selector.fill.geometry.positions = ...
414+
415+
edge_geometry = selector.edge.geometry
416+
417+
# Need larger buffer?
418+
if len(value) > edge_geometry.positions.nitems:
419+
arr = np.zeros((edge_geometry.positions.nitems * 2, 3), np.float32)
420+
edge_geometry.positions = gfx.Buffer(arr)
421+
422+
edge_geometry.positions.data[: len(value)] = value
423+
edge_geometry.positions.draw_range = 0, len(value)
424+
edge_geometry.positions.update_full()
425+
426+
# send event
427+
if len(self._event_handlers) < 1:
428+
return
429+
430+
event = GraphicFeatureEvent("selection", {"value": self.value})
431+
432+
event.get_selected_indices = selector.get_selected_indices
433+
event.get_selected_data = selector.get_selected_data
434+
435+
# calls any events
436+
self._call_event_handlers(event)

fastplotlib/graphics/image.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from ..utils import quick_min_max
77
from ._base import Graphic
8-
from .selectors import LinearSelector, LinearRegionSelector, RectangleSelector
8+
from .selectors import LinearSelector, LinearRegionSelector, RectangleSelector, PolygonSelector
99
from .features import (
1010
TextureArray,
1111
ImageCmap,
@@ -437,3 +437,46 @@ def add_rectangle_selector(
437437
selector.offset = selector.offset + (0.0, 0.0, self.offset[-1] + 1)
438438

439439
return selector
440+
441+
def add_polygon_selector(
442+
self,
443+
selection: List[tuple[float, float]] = None,
444+
fill_color=(0, 0, 0.35, 0.2),
445+
**kwargs,
446+
) -> PolygonSelector:
447+
"""
448+
Add a :class:`.PolygonSelector`.
449+
450+
Selectors are just ``Graphic`` objects, so you can manage, remove, or delete them
451+
from a plot area just like any other ``Graphic``.
452+
453+
Parameters
454+
----------
455+
selection: List of positions, optional
456+
initial points for the polygon
457+
458+
"""
459+
# default selection is 25% of the diagonal
460+
if selection is None:
461+
diagonal = math.sqrt(
462+
self._data.value.shape[0] ** 2 + self._data.value.shape[1] ** 2
463+
)
464+
465+
selection = (0, int(diagonal / 4), 0, int(diagonal / 4))
466+
467+
# min/max limits are image shape
468+
# rows are ys, columns are xs
469+
limits = (0, self._data.value.shape[1], 0, self._data.value.shape[0])
470+
471+
selector = PolygonSelector(
472+
fill_color=fill_color,
473+
parent=self,
474+
**kwargs,
475+
)
476+
477+
self._plot_area.add_graphic(selector, center=False)
478+
479+
# place above this graphic
480+
selector.offset = selector.offset + (0.0, 0.0, self.offset[-1] + 1)
481+
482+
return selector

fastplotlib/graphics/line.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import pygfx
66

77
from ._positions_base import PositionsGraphic
8-
from .selectors import LinearRegionSelector, LinearSelector, RectangleSelector
8+
from .selectors import LinearRegionSelector, LinearSelector, RectangleSelector, PolygonSelector
99
from .features import (
1010
Thickness,
1111
VertexPositions,
@@ -245,7 +245,7 @@ def add_rectangle_selector(
245245
self,
246246
selection: tuple[float, float, float, float] = None,
247247
**kwargs,
248-
) -> RectangleSelector:
248+
) -> RectangleSelector:
249249
"""
250250
Add a :class:`.RectangleSelector`.
251251
@@ -288,6 +288,31 @@ def add_rectangle_selector(
288288

289289
return selector
290290

291+
def add_polygon_selector(
292+
self,
293+
selection: List[tuple[float, float]] = None,
294+
**kwargs,
295+
) -> PolygonSelector:
296+
"""
297+
Add a :class:`.PolygonSelector`.
298+
299+
Selectors are just ``Graphic`` objects, so you can manage, remove, or delete them from a
300+
plot area just like any other ``Graphic``.
301+
302+
Parameters
303+
----------
304+
selection: (float, float, float, float), optional
305+
initial (xmin, xmax, ymin, ymax) of the selection
306+
"""
307+
selector = PolygonSelector(
308+
parent=self,
309+
**kwargs,
310+
)
311+
312+
self._plot_area.add_graphic(selector, center=False)
313+
314+
return selector
315+
291316
# TODO: this method is a bit of a mess, can refactor later
292317
def _get_linear_selector_init_args(
293318
self, axis: str, padding

fastplotlib/graphics/line_collection.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from ..utils import parse_cmap_values
88
from ._collection_base import CollectionIndexer, GraphicCollection, CollectionFeature
99
from .line import LineGraphic
10-
from .selectors import LinearRegionSelector, LinearSelector, RectangleSelector
10+
from .selectors import LinearRegionSelector, LinearSelector, RectangleSelector, PolygonSelector
1111

1212

1313
class _LineCollectionProperties:
@@ -486,6 +486,36 @@ def add_rectangle_selector(
486486

487487
return selector
488488

489+
def add_polygon_selector(
490+
self,
491+
selection: List[tuple[float, float]] = None,
492+
**kwargs,
493+
) -> PolygonSelector:
494+
"""
495+
Add a :class:`.PolygonSelector`. Selectors are just ``Graphic`` objects, so you can manage,
496+
remove, or delete them from a plot area just like any other ``Graphic``.
497+
498+
Parameters
499+
----------
500+
selection: List of positions, optional
501+
initial points for the polygon
502+
"""
503+
bbox = self.world_object.get_world_bounding_box()
504+
505+
506+
if selection is not None:
507+
selection = [] # TODO: fill selection
508+
509+
510+
selector = PolygonSelector(
511+
parent=self,
512+
**kwargs,
513+
)
514+
515+
self._plot_area.add_graphic(selector, center=False)
516+
517+
return selector
518+
489519
def _get_linear_selector_init_args(self, axis, padding):
490520
# use bbox to get size and center
491521
bbox = self.world_object.get_world_bounding_box()

0 commit comments

Comments
 (0)