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 47ec02a

Browse filesBrowse files
authored
separate array logic and graphic logic in ImageWidget (#868)
* start separating iw plotting and array logic * some more basics down * comment * collapse into just having a window function, no frame_function * progress * placeholder for computing histogram * formatting * remove spaghetti * more progress * basics working :D * black * most of the basics work in iw * fix * progress * progress but still broken * flippin display dims works * camera scale must be positive for MIP rendering * a very difficult to encounter iterator bug! * patch iterator caveats * mostly worksgit status * add ArrayProtocol * rename * fixes * set camera orthogonal to xy plane when going from 3d -> 2d * naming, cleaning * cleanup, correct way to push and pop dims * quality of life improvements * new histogram lut tool * new hlut tool * imagewidget rgb toggle works * more progress * support rgb(a) image volumes * ImageGraphic cleanup * cleanup, docs * fix * updates * new per-data array properties work * black formatting * fixes and other things * typing tweaks * better iterator, fix bugs * fixes * show tooltips in right clck menu * ignore nans and inf for histogram * histogram of zeros * docstrings * fix imgui pixels * iw indices event handlers only get a tuple of the indices * bugfix * fix cmap setter * spatial_func better name * bugfix * hist specify quantile * fix typos (#991) * fix typos * add rendercanvas to intersphinx_mapping * nd-iw backup * correct ImageGraphic w.r.t. ndw * last fixes in ndi
1 parent 9efd69c commit 47ec02a
Copy full SHA for 47ec02a

19 files changed

+2,089-374Lines changed: 2089 additions & 374 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

‎.github/workflows/docs-deploy.yml‎

Copy file name to clipboardExpand all lines: .github/workflows/docs-deploy.yml
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ jobs:
4949
- name: build docs
5050
run: |
5151
cd docs
52-
RTD_BUILD=1 make html SPHINXOPTS="-W --keep-going"
52+
DOCS_BUILD=1 make html SPHINXOPTS="-W --keep-going"
5353
5454
# set environment variable `DOCS_VERSION_DIR` to either the pr-branch name, "dev", or the release version tag
5555
- name: set output pr
Collapse file

‎fastplotlib/graphics/_base.py‎

Copy file name to clipboardExpand all lines: fastplotlib/graphics/_base.py
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ def __init__(
178178
self._alpha_mode = AlphaMode(alpha_mode)
179179
self._visible = Visible(visible)
180180
self._block_events = False
181+
self._block_handlers = list()
181182

182183
self._axes: Axes = None
183184

@@ -274,6 +275,11 @@ def block_events(self) -> bool:
274275
def block_events(self, value: bool):
275276
self._block_events = value
276277

278+
@property
279+
def block_handlers(self) -> list:
280+
"""Used to block event handlers for a graphic and prevent recursion."""
281+
return self._block_handlers
282+
277283
@property
278284
def world_object(self) -> pygfx.WorldObject:
279285
"""Associated pygfx WorldObject. Always returns a proxy, real object cannot be accessed directly."""
@@ -440,6 +446,9 @@ def _handle_event(self, callback, event: pygfx.Event):
440446
if self.block_events:
441447
return
442448

449+
if callback in self._block_handlers:
450+
return
451+
443452
if event.type in self._features:
444453
# for feature events
445454
event._target = self.world_object
Collapse file

‎fastplotlib/graphics/features/_base.py‎

Copy file name to clipboardExpand all lines: fastplotlib/graphics/features/_base.py
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ def __repr__(self):
318318
def block_reentrance(set_value):
319319
# decorator to block re-entrant set_value methods
320320
# useful when creating complex, circular, bidirectional event graphs
321-
def set_value_wrapper(self: GraphicFeature, graphic_or_key, value):
321+
def set_value_wrapper(self: GraphicFeature, graphic_or_key, value, **kwargs):
322322
"""
323323
wraps GraphicFeature.set_value
324324
@@ -334,7 +334,7 @@ def set_value_wrapper(self: GraphicFeature, graphic_or_key, value):
334334
try:
335335
# block re-execution of set_value until it has *fully* finished executing
336336
self._reentrant_block = True
337-
set_value(self, graphic_or_key, value)
337+
set_value(self, graphic_or_key, value, **kwargs)
338338
except Exception as exc:
339339
# raise original exception
340340
raise exc # set_value has raised. The line above and the lines 2+ steps below are probably more relevant!
Collapse file

‎fastplotlib/graphics/features/_selection_features.py‎

Copy file name to clipboardExpand all lines: fastplotlib/graphics/features/_selection_features.py
+4-2Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ def axis(self) -> str:
118118
return self._axis
119119

120120
@block_reentrance
121-
def set_value(self, selector, value: Sequence[float]):
121+
def set_value(self, selector, value: Sequence[float], *, change: str = "full"):
122122
"""
123123
Set start, stop range of selector
124124
@@ -182,7 +182,9 @@ def set_value(self, selector, value: Sequence[float]):
182182
if len(self._event_handlers) < 1:
183183
return
184184

185-
event = GraphicFeatureEvent(self._property_name, {"value": self.value})
185+
event = GraphicFeatureEvent(
186+
self._property_name, {"value": self.value, "change": change}
187+
)
186188

187189
event.get_selected_indices = selector.get_selected_indices
188190
event.get_selected_data = selector.get_selected_data
Collapse file

‎fastplotlib/graphics/image.py‎

Copy file name to clipboardExpand all lines: fastplotlib/graphics/image.py
+10-5Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,11 @@ def __init__(
162162
self._cmap_interpolation = ImageCmapInterpolation(cmap_interpolation)
163163

164164
# set map to None for RGB images
165-
if self._data.value.ndim > 2:
165+
if self._data.value.ndim == 3:
166166
self._cmap = None
167167
_map = None
168-
else:
168+
169+
elif self._data.value.ndim == 2:
169170
# use TextureMap for grayscale images
170171
self._cmap = ImageCmap(cmap)
171172

@@ -174,6 +175,12 @@ def __init__(
174175
filter=self._cmap_interpolation.value,
175176
wrap="clamp-to-edge",
176177
)
178+
else:
179+
raise ValueError(
180+
f"ImageGraphic `data` must have 2 dimensions for grayscale images, or 3 dimensions for RGB(A) images.\n"
181+
f"You have passed a a data array with: {self._data.value.ndim} dimensions, "
182+
f"and of shape: {self._data.value.shape}"
183+
)
177184

178185
# one common material is used for every Texture chunk
179186
self._material = pygfx.ImageBasicMaterial(
@@ -275,8 +282,6 @@ def cmap(self) -> str | None:
275282
if self._cmap is not None:
276283
return self._cmap.value
277284

278-
return None
279-
280285
@cmap.setter
281286
def cmap(self, name: str):
282287
if self.data.value.ndim > 2:
@@ -312,7 +317,7 @@ def interpolation(self, value: str):
312317

313318
@property
314319
def cmap_interpolation(self) -> str:
315-
"""cmap interpolation method"""
320+
"""cmap interpolation method, 'linear' or 'nearest'. Used only for grayscale images"""
316321
return self._cmap_interpolation.value
317322

318323
@cmap_interpolation.setter
Collapse file

‎fastplotlib/graphics/image_volume.py‎

Copy file name to clipboardExpand all lines: fastplotlib/graphics/image_volume.py
+10-4Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -204,18 +204,24 @@ def __init__(
204204
self._vmax = ImageVmax(vmax)
205205

206206
self._interpolation = ImageInterpolation(interpolation)
207+
self._cmap_interpolation = ImageCmapInterpolation(cmap_interpolation)
207208

208-
# TODO: I'm assuming RGB volume images aren't supported???
209209
# use TextureMap for grayscale images
210210
self._cmap = ImageCmap(cmap)
211-
self._cmap_interpolation = ImageCmapInterpolation(cmap_interpolation)
212-
213211
self._texture_map = pygfx.TextureMap(
214212
self._cmap.texture,
215213
filter=self._cmap_interpolation.value,
216214
wrap="clamp-to-edge",
217215
)
218216

217+
if self._data.value.ndim not in (3, 4):
218+
raise ValueError(
219+
f"ImageVolumeGraphic `data` must have 3 dimensions for grayscale images, "
220+
f"or 4 dimensions for RGB(A) images.\n"
221+
f"You have passed a a data array with: {self._data.value.ndim} dimensions, "
222+
f"and of shape: {self._data.value.shape}"
223+
)
224+
219225
self._plane = VolumeSlicePlane(plane)
220226
self._threshold = VolumeIsoThreshold(threshold)
221227
self._step_size = VolumeIsoStepSize(step_size)
@@ -301,7 +307,7 @@ def mode(self, mode: str):
301307

302308
@property
303309
def cmap(self) -> str:
304-
"""Get or set colormap name"""
310+
"""Get or set colormap name, only used for grayscale images"""
305311
return self._cmap.value
306312

307313
@cmap.setter
Collapse file

‎fastplotlib/graphics/selectors/_linear_region.py‎

Copy file name to clipboardExpand all lines: fastplotlib/graphics/selectors/_linear_region.py
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -472,9 +472,9 @@ def _move_graphic(self, move_info: MoveInfo):
472472
if move_info.source == self._edges[0]:
473473
# change only left or bottom bound
474474
new_min = min(cur_min + delta, cur_max)
475-
self._selection.set_value(self, (new_min, cur_max))
475+
self._selection.set_value(self, (new_min, cur_max), change="min")
476476

477477
elif move_info.source == self._edges[1]:
478478
# change only right or top bound
479479
new_max = max(cur_max + delta, cur_min)
480-
self._selection.set_value(self, (cur_min, new_max))
480+
self._selection.set_value(self, (cur_min, new_max), change="max")
Collapse file

‎fastplotlib/graphics/utils.py‎

Copy file name to clipboardExpand all lines: fastplotlib/graphics/utils.py
+12-3Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
from contextlib import contextmanager
2+
from typing import Callable, Iterable
23

34
from ._base import Graphic
45

56

67
@contextmanager
7-
def pause_events(*graphics: Graphic):
8+
def pause_events(*graphics: Graphic, event_handlers: Iterable[Callable] = None):
89
"""
910
Context manager for pausing Graphic events.
1011
12+
Optionally pass in only specific event handlers which are blocked. Other events for the graphic will not be blocked.
13+
1114
Examples
1215
--------
1316
@@ -30,8 +33,14 @@ def pause_events(*graphics: Graphic):
3033
original_vals = [g.block_events for g in graphics]
3134

3235
for g in graphics:
33-
g.block_events = True
36+
if event_handlers is not None:
37+
g.block_handlers.extend([e for e in event_handlers])
38+
else:
39+
g.block_events = True
3440
yield
3541

3642
for g, value in zip(graphics, original_vals):
37-
g.block_events = value
43+
if event_handlers is not None:
44+
g.block_handlers.clear()
45+
else:
46+
g.block_events = value
Collapse file

‎fastplotlib/layouts/_figure.py‎

Copy file name to clipboardExpand all lines: fastplotlib/layouts/_figure.py
+18-16Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ def _render(self, draw=True):
539539

540540
# call the animation functions before render
541541
self._call_animate_functions(self._animate_funcs_pre)
542-
for subplot in self:
542+
for subplot in self._subplots.ravel():
543543
subplot._render()
544544

545545
# overlay render pass
@@ -606,14 +606,14 @@ def show(
606606
sidecar_kwargs = dict()
607607

608608
# flip y-axis if ImageGraphics are present
609-
for subplot in self:
609+
for subplot in self._subplots.ravel():
610610
for g in subplot.graphics:
611611
if isinstance(g, ImageGraphic):
612612
subplot.camera.local.scale_y *= -1
613613
break
614614

615615
if autoscale:
616-
for subplot in self:
616+
for subplot in self._subplots.ravel():
617617
if maintain_aspect is None:
618618
_maintain_aspect = subplot.camera.maintain_aspect
619619
else:
@@ -622,7 +622,7 @@ def show(
622622

623623
# set axes visibility if False
624624
if not axes_visible:
625-
for subplot in self:
625+
for subplot in self._subplots.ravel():
626626
subplot.axes.visible = False
627627

628628
# parse based on canvas type
@@ -646,15 +646,15 @@ def show(
646646
elif self.canvas.__class__.__name__ == "OffscreenRenderCanvas":
647647
# for test and docs gallery screenshots
648648
self._fpl_reset_layout()
649-
for subplot in self:
649+
for subplot in self._subplots.ravel():
650650
subplot.axes.update_using_camera()
651651

652652
# render call is blocking only on github actions for some reason,
653653
# but not for rtd build, this is a workaround
654654
# for CI tests, the render call works if it's in test_examples
655655
# but it is necessary for the gallery images too so that's why this check is here
656-
if "RTD_BUILD" in os.environ.keys():
657-
if os.environ["RTD_BUILD"] == "1":
656+
if "DOCS_BUILD" in os.environ.keys():
657+
if os.environ["DOCS_BUILD"] == "1":
658658
self._render()
659659

660660
else: # assume GLFW
@@ -770,7 +770,7 @@ def clear_animations(self, removal: str = None):
770770

771771
def clear(self):
772772
"""Clear all Subplots"""
773-
for subplot in self:
773+
for subplot in self._subplots.ravel():
774774
subplot.clear()
775775

776776
def export_numpy(self, rgb: bool = False) -> np.ndarray:
@@ -929,18 +929,20 @@ def __getitem__(self, index: str | int | tuple[int, int]) -> Subplot:
929929
return subplot
930930
raise IndexError(f"no subplot with given name: {index}")
931931

932+
if isinstance(index, (int, np.integer)):
933+
return self._subplots.ravel()[index]
934+
932935
if isinstance(self.layout, GridLayout):
933936
return self._subplots[index[0], index[1]]
934937

935-
return self._subplots[index]
938+
raise TypeError(
939+
f"Can index figure using <str> subplot name, numerical <int> subplot index, or a "
940+
f"tuple[int, int] if the layout is a grid"
941+
)
936942

937943
def __iter__(self):
938-
self._current_iter = iter(range(len(self)))
939-
return self
940-
941-
def __next__(self) -> Subplot:
942-
pos = self._current_iter.__next__()
943-
return self._subplots.ravel()[pos]
944+
for subplot in self._subplots.ravel():
945+
yield subplot
944946

945947
def __len__(self):
946948
"""number of subplots"""
@@ -955,6 +957,6 @@ def __repr__(self):
955957
return (
956958
f"fastplotlib.{self.__class__.__name__}"
957959
f" Subplots:\n"
958-
f"\t{newline.join(subplot.__str__() for subplot in self)}"
960+
f"\t{newline.join(subplot.__str__() for subplot in self._subplots.ravel())}"
959961
f"\n"
960962
)
Collapse file

‎fastplotlib/layouts/_plot_area.py‎

Copy file name to clipboardExpand all lines: fastplotlib/layouts/_plot_area.py
+4-1Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,10 @@ def controller(self, new_controller: str | pygfx.Controller):
233233
# pygfx plans on refactoring viewports anyways
234234
if self.parent is not None:
235235
if self.parent.__class__.__name__.endswith("Figure"):
236-
for subplot in self.parent:
236+
# always use figure._subplots.ravel() in internal fastplotlib code
237+
# otherwise if we use `for subplot in figure`, this could conflict
238+
# with a user's iterator where they are doing `for subplot in figure` !!!
239+
for subplot in self.parent._subplots.ravel():
237240
if subplot.camera in cameras_list:
238241
new_controller.register_events(subplot.viewport)
239242
subplot._controller = new_controller

0 commit comments

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