Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f35f185

Browse filesBrowse files
committed
Merge branch 'ndwidget' of https://github.com/fastplotlib/fastplotlib into ndwidget
2 parents 35de191 + 427baae commit f35f185
Copy full SHA for f35f185

4 files changed

+100-27Lines changed: 100 additions & 27 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎examples/selection_tools/visibility_selector.py‎

Copy file name to clipboardExpand all lines: examples/selection_tools/visibility_selector.py
+4-5Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,6 @@ def image_clicked(session, ev):
152152
# Create selectors
153153
# image highlight selector for this session
154154
image_selector = fpl.ImageHighlightSelector(
155-
ndi.graphic, # target graphic, you can also add more target graphics later
156-
# as long as they are in the same "selection space", ex: each movie for single-session
157-
# each selector manages ONE buffer, so the same pixels will be highlighted on all graphics
158-
# targetted by a selector.
159155
lut="tab10",
160156
selection_options={"pixels": contours}, # pre-loaded selection options
161157
options_alpha=0.1, # unselected contours shown with low alpha
@@ -170,7 +166,10 @@ def image_clicked(session, ev):
170166
ndt.graphic, lut="tab10", lut_wrap="repeat"
171167
)
172168

173-
# image selector targets the image graphic for this session
169+
# target graphic, you can also add more target graphics later
170+
# as long as they are in the same "selection space", ex: each movie for single-session
171+
# each selector manages ONE buffer, so the same pixels will be highlighted on all graphics
172+
# targetted by a selector.
174173
image_selector.add_graphic(ndi.graphic)
175174
# when image is double clicked, calls the handler
176175
ndi.graphic.add_event_handler(partial(image_clicked, session_index), "double_click")
Collapse file

‎fastplotlib/graphics/features/_image.py‎

Copy file name to clipboardExpand all lines: fastplotlib/graphics/features/_image.py
+4-1Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def __init__(
4343
data,
4444
property_name: str = "data",
4545
cpu_buffer: bool = True,
46+
usage: wgpu.TextureUsage = 0,
4647
colorspace: ColorspacesRGB = ColorspacesRGB.srgb,
4748
):
4849
super().__init__(property_name=property_name)
@@ -60,9 +61,10 @@ def __init__(
6061
# create a local buffer
6162
self._value = np.empty(data.shape, dtype=data.dtype)
6263
self.value[:] = data[:]
64+
usage = usage
6365
else:
6466
self._value = None
65-
usage = wgpu.TextureUsage.COPY_DST
67+
usage = wgpu.TextureUsage.COPY_DST | usage
6668
# auto-determine format, adapted from pygfx.Texture
6769
element_format = get_element_format_from_numpy_array(data)
6870
if element_format is None:
@@ -106,6 +108,7 @@ def __init__(
106108
self.value[slicer],
107109
dim=2,
108110
colorspace=colorspace,
111+
usage=usage
109112
)
110113
else:
111114
# we only supply the size
Collapse file

‎fastplotlib/graphics/selectors/_highlight_selector.py‎

Copy file name to clipboardExpand all lines: fastplotlib/graphics/selectors/_highlight_selector.py
+13-3Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,7 @@ def __init__(
570570
super().__init__(color=color, lut=lut, alpha=alpha, lut_wrap=lut_wrap)
571571

572572
self._selection: dict[str, list] = dict()
573-
self._selected_indices: list[int] = list()
573+
self._selected_indices: list[int | None] = list()
574574
self._options_color = options_color
575575
self._options_alpha = float(options_alpha)
576576

@@ -669,7 +669,7 @@ def options_alpha(self, value: float) -> None:
669669
self._update_all_graphics()
670670

671671
@property
672-
def selection(self) -> tuple[int, ...] | dict[str, tuple]:
672+
def selection(self) -> tuple[int | None, ...] | dict[str, tuple]:
673673
"""
674674
In options mode: tuple of selection option indices.
675675
In free mode: dict of selection items.
@@ -690,7 +690,7 @@ def selection(self, value: Iterable[int] | dict[Literal["rows", "cols", "pixels"
690690
self._selected_indices = [value]
691691

692692
else:
693-
self._selected_indices = [int(i) for i in value]
693+
self._selected_indices = [int(i) if i is not None else None for i in value]
694694

695695
else:
696696
if not value:
@@ -837,15 +837,23 @@ def _create_mask(self, n_rows: int, n_cols: int) -> np.ndarray:
837837
)
838838
# start=1 since 0 indicates unselected placeholder value
839839
for i, (rs, cs) in enumerate(zip(sel["rows"], sel["cols"]), start=1):
840+
if rs is None or cs in None:
841+
continue
840842
mask[rs, cs] = i
841843
elif "rows" in sel:
842844
for i, rs in enumerate(sel["rows"], start=1):
845+
if rs is None:
846+
continue
843847
mask[rs, :] = i
844848
elif "cols" in sel:
845849
for i, cs in enumerate(sel["cols"], start=1):
850+
if cs in None:
851+
continue
846852
mask[:, cs] = i
847853
elif "pixels" in sel:
848854
for i, px in enumerate(sel["pixels"], start=1):
855+
if px is None:
856+
continue
849857
arr = np.asarray(px)
850858
mask[arr[:, 0], arr[:, 1]] = i
851859
return mask
@@ -868,6 +876,8 @@ def _fill_lut(self) -> None:
868876
)
869877
current_lut[:, -1] *= self._alpha
870878
for i, sel in enumerate(self._selected_indices):
879+
if sel is None:
880+
continue
871881
lut_buffer[sel] = current_lut[i]
872882
else:
873883
n = self._len_dict(self._selection)
Collapse file

‎fastplotlib/graphics/selectors/_visibility_selector.py‎

Copy file name to clipboardExpand all lines: fastplotlib/graphics/selectors/_visibility_selector.py
+79-18Lines changed: 79 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ def _validate_int_collection(value, name: str) -> set | int:
2121

2222
s = set(value)
2323

24-
if not all(isinstance(i, Integral) for i in s):
25-
raise TypeError(f"{name} must contain only integers, got: {s!r}")
24+
if not all(isinstance(i, Integral) or i is None for i in s):
25+
raise TypeError(f"{name} must contain only integers or None, got: {s!r}")
2626

2727
return value
2828

@@ -69,7 +69,7 @@ def __init__(
6969
raise ValueError(f"lut_wrap must be 'fixed' or 'repeat', got {lut_wrap!r}")
7070

7171
self._collection = collection
72-
self._selection: list[int] = []
72+
self._selection: list[int | None] = []
7373
self._event_handlers: list[Callable] = []
7474
self._lut_wrap = lut_wrap
7575

@@ -109,16 +109,18 @@ def __del__(self):
109109
g.colors = self._original_colors[i]
110110

111111
@property
112-
def selection(self) -> tuple[int, ...]:
112+
def selection(self) -> tuple[int | None, ...]:
113113
"""Get or set the selection"""
114114
return tuple(self._selection)
115115

116116
@selection.setter
117-
def selection(self, new_selection: Iterable[int] | int):
117+
def selection(self, new_selection: Iterable[int | None] | int):
118118
if new_selection:
119119
_validate_int_collection(new_selection, "selection")
120120

121121
for index in self._selection:
122+
if index is None:
123+
continue
122124
# set any selected things to be invisible
123125
self._collection.graphics[index].visible = False
124126

@@ -128,6 +130,8 @@ def selection(self, new_selection: Iterable[int] | int):
128130
self._selection = list(new_selection) if new_selection else list()
129131

130132
for index in self._selection:
133+
if index is None:
134+
continue
131135
# set the new selection to be visible
132136
self._collection.graphics[index].visible = True
133137

@@ -139,13 +143,15 @@ def selection(self, new_selection: Iterable[int] | int):
139143

140144
def append(self, item: int):
141145
"""Add an index to the selection. Already-selected indices are skipped."""
142-
if not isinstance(item, Integral):
143-
raise TypeError(f"item must be an integer, got {type(item)}")
146+
if not isinstance(item, Integral) and item is not None:
147+
raise TypeError(f"item must be an integer or None, got {type(item)}")
144148

145-
if item in self._selection:
149+
if item in self._selection and item is not None:
146150
return
147151

148-
self._collection.graphics[item].visible = True
152+
if item is not None:
153+
self._collection.graphics[item].visible = True
154+
149155
self._selection.append(item)
150156

151157
if self._is_stack:
@@ -171,13 +177,41 @@ def remove(self, item: int):
171177
self._apply_lut()
172178
self._emit({"value": list(self._selection)})
173179

180+
def pop(self, index: int):
181+
"""pop item at the given index"""
182+
183+
if not isinstance(index, Integral):
184+
raise TypeError(
185+
f"pop argument must be an integer, got: {type(index).__name__}"
186+
)
187+
188+
if index >= len(self):
189+
raise IndexError(
190+
f"index: {index} out of bounds for {self.__class__.__name__} with length: {len(self)}"
191+
)
192+
193+
item = self._selection[index]
194+
if item is not None:
195+
self._collection.graphics[item].visible = False
196+
197+
self._selection.pop(index)
198+
199+
if self._is_stack:
200+
self._restack()
201+
202+
self._apply_lut()
203+
self._emit({"value": list(self._selection)})
204+
174205
def clear(self) -> None:
175206
"""Hide all graphics. Stack offsets are left as-is."""
176207
for idx in self._selection:
208+
if idx is None:
209+
continue
177210
self._collection.graphics[idx].visible = False
178211

179212
self._selection = list()
180213
self._emit({"value": []})
214+
181215
@property
182216
def lut(self) -> np.ndarray | None:
183217
"""Optional per-item colors, shape ``(n, 4)`` float32 RGBA"""
@@ -201,6 +235,8 @@ def _apply_lut(self) -> None:
201235
color=None, lut=self._lut, n=len(self._selection), lut_wrap=self._lut_wrap
202236
)
203237
for sel_index, graphic_index in enumerate(self._selection):
238+
if graphic_index is None:
239+
continue
204240
self._collection.graphics[graphic_index].colors = colors[sel_index]
205241

206242
def _restack(self) -> None:
@@ -209,6 +245,9 @@ def _restack(self) -> None:
209245

210246
distance = 0.0
211247
for index in self._selection:
248+
if index is None:
249+
continue
250+
212251
g = self._collection.graphics[index]
213252
offset = list(g.offset)
214253
offset[ax_i] = distance
@@ -243,10 +282,7 @@ def __iter__(self):
243282
return iter(self._selection)
244283

245284
def __repr__(self) -> str:
246-
return (
247-
f"VisibilitySelector\n"
248-
f"selection: {self._selection}"
249-
)
285+
return f"VisibilitySelector\n" f"selection: {self._selection}"
250286

251287

252288
class ImageVisibilitySelector:
@@ -334,12 +370,12 @@ def selection(self, value: Iterable[int]):
334370
self._update_material()
335371
self._emit({"value": tuple(self._selection)})
336372

337-
def append(self, item) -> None:
373+
def append(self, item: int | None):
338374
"""add a row/col index to the selection"""
339-
if not isinstance(item, Integral):
340-
raise TypeError(f"item must be an integer, got {type(item)}")
375+
if not isinstance(item, Integral) and item is not None:
376+
raise TypeError(f"item must be an integer or None, got {type(item)}")
341377

342-
if item in self._selection:
378+
if item in self._selection and item is not None:
343379
return
344380

345381
self._selection.append(item)
@@ -359,6 +395,22 @@ def remove(self, item) -> None:
359395
self._update_material()
360396
self._emit({"value": list(self._selection)})
361397

398+
def pop(self, index: int):
399+
"""pop item at the given index"""
400+
if not isinstance(index, Integral):
401+
raise TypeError(
402+
f"pop argument must be an integer, got: {type(index).__name__}"
403+
)
404+
405+
if index >= len(self):
406+
raise IndexError(
407+
f"index: {index} out of bounds for {self.__class__.__name__} with length: {len(self)}"
408+
)
409+
410+
self._selection.pop(index)
411+
self._update_material()
412+
self._emit({"value": list(self._selection)})
413+
362414
def clear(self) -> None:
363415
"""Clear the selection (all invisible, fpl_n_visible=0)."""
364416
self._selection = list()
@@ -369,7 +421,16 @@ def _update_material(self) -> None:
369421
mat = self._graphic._material
370422
n = len(self._selection)
371423
if n > 0:
372-
mat._vis_lut_buffer.data[:n] = np.array(self._selection, dtype=np.uint32)
424+
mat._vis_lut_buffer.data[:n] = np.array(
425+
list(
426+
map(
427+
# 0xFFFFFFFF, 2^32 - 1, indicates None vals and shader discard
428+
lambda x: x if x is not None else np.uint32(0xFFFFFFFF),
429+
self._selection,
430+
)
431+
),
432+
dtype=np.uint32,
433+
)
373434

374435
mat._vis_lut_buffer.update_range()
375436
mat.uniform_buffer.data["fpl_n_visible"] = np.uint32(n)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.