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 570c076

Browse filesBrowse files
committed
make most imagewidget methods private, most attributes as read-only properties
1 parent 0e3cf16 commit 570c076
Copy full SHA for 570c076

File tree

1 file changed

+118
-53
lines changed
Filter options

1 file changed

+118
-53
lines changed

‎fastplotlib/widgets/image.py

Copy file name to clipboardExpand all lines: fastplotlib/widgets/image.py
+118-53Lines changed: 118 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@
1616
}
1717

1818

19-
def calc_gridshape(n):
19+
def _calc_gridshape(n):
2020
sr = np.sqrt(n)
2121
return (
2222
int(np.ceil(sr)),
2323
int(np.round(sr))
2424
)
2525

2626

27-
def is_arraylike(obj) -> bool:
27+
def _is_arraylike(obj) -> bool:
2828
"""
2929
Checks if the object is array-like.
3030
For now just checks if obj has `__getitem__()`
@@ -41,6 +41,66 @@ def is_arraylike(obj) -> bool:
4141

4242

4343
class ImageWidget:
44+
@property
45+
def plot(self) -> Union[Plot, GridPlot]:
46+
"""
47+
The plotter used by the ImageWidget. Either a simple ``Plot`` or ``GridPlot``.
48+
"""
49+
return self._plot
50+
51+
@property
52+
def data(self) -> List[np.ndarray]:
53+
"""data currently displayed in the widget"""
54+
return self._data
55+
56+
@property
57+
def ndim(self) -> int:
58+
"""number of dimensions in the image data displayed in the widget"""
59+
return self._ndim
60+
61+
@property
62+
def dims_order(self) -> List[str]:
63+
"""dimension order of the data displayed in the widget"""
64+
return self._dims_order
65+
66+
@property
67+
def sliders(self) -> List[IntSlider]:
68+
"""the slider instances used by the widget for indexing the desired dimensions"""
69+
return self._sliders
70+
71+
@property
72+
def slider_dims(self) -> List[str]:
73+
"""the dimensions that the sliders index"""
74+
return self._slider_dims
75+
76+
@property
77+
def current_index(self) -> Dict[str, int]:
78+
return self._current_index
79+
80+
@current_index.setter
81+
def current_index(self, index: Dict[str, int]):
82+
"""
83+
Set the current index
84+
85+
Parameters
86+
----------
87+
index: Dict[str, int]
88+
| ``dict`` for indexing each dimension, provide a ``dict`` with indices for all dimensions used by sliders
89+
or only a subset of dimensions used by the sliders.
90+
| example: if you have sliders for dims "t" and "z", you can pass either ``{"t": 10}`` to index to position
91+
10 on dimension "t" or ``{"t": 5, "z": 20}`` to index to position 5 on dimension "t" and position 20 on
92+
dimension "z" simultaneously.
93+
"""
94+
if not set(index.keys()).issubset(set(self._current_index.keys())):
95+
raise KeyError(
96+
f"All dimension keys for setting `current_index` must be present in the widget sliders. "
97+
f"The dimensions currently used for sliders are: {list(self.current_index.keys())}"
98+
)
99+
self._current_index.update(index)
100+
for ig, data in zip(self.image_graphics, self.data):
101+
frame = self._get_2d_slice(data, self._current_index)
102+
ig.update_data(frame)
103+
44104
def __init__(
45105
self,
46106
data: Union[np.ndarray, List[np.ndarray]],
@@ -75,14 +135,15 @@ def __init__(
75135
dims_order: Optional[Union[str, Dict[np.ndarray, str]]]
76136
| ``str`` or a dict mapping to indicate dimension order
77137
| a single ``str`` if ``data`` is a single array, or a list of arrays with the same dimension order
138+
| examples: ``"xyt"``, ``"tzxy"``
78139
| ``dict`` mapping of ``{array: axis_order}`` if specific arrays have a non-default axes order.
79-
| examples: "xyt", "tzxy"
140+
| examples: ``{some_array: "tzxy", another_array: "xytz"}``
80141
81142
slider_dims: Optional[Union[str, int, List[Union[str, int]]]]
82143
| The dimensions for which to create a slider
83144
| can be a single ``str`` such as **"t"**, **"z"** or a numerical ``int`` that indexes the desired dimension
84145
| can also be a list of ``str`` or ``int`` if multiple sliders are desired for multiple dimensions
85-
| examples: "t", ["t", "z"]
146+
| examples: ``"t"``, ``["t", "z"]``
86147
87148
slice_avg: Dict[Union[int, str], int]
88149
| average one or more dimensions using a given window
@@ -101,13 +162,13 @@ def __init__(
101162
"""
102163
if isinstance(data, list):
103164
# verify that it's a list of np.ndarray
104-
if all([is_arraylike(d) for d in data]):
165+
if all([_is_arraylike(d) for d in data]):
105166
if grid_shape is None:
106-
grid_shape = calc_gridshape(len(data))
167+
grid_shape = _calc_gridshape(len(data))
107168

108169
# verify that user-specified grid shape is large enough for the number of image arrays passed
109170
elif grid_shape[0] * grid_shape[1] < len(data):
110-
grid_shape = calc_gridshape(len(data))
171+
grid_shape = _calc_gridshape(len(data))
111172
warn(f"Invalid `grid_shape` passed, setting grid shape to: {grid_shape}")
112173

113174
_ndim = [d.ndim for d in data]
@@ -119,22 +180,22 @@ def __init__(
119180
f"Number of dimensions of all data arrays must match, your ndims are: {_ndim}"
120181
)
121182

122-
self.data: List[np.ndarray] = data
123-
self.ndim = self.data[0].ndim # all ndim must be same
183+
self._data: List[np.ndarray] = data
184+
self._ndim = self.data[0].ndim # all ndim must be same
124185

125-
self.plot_type = "grid"
186+
self._plot_type = "grid"
126187

127188
else:
128189
raise TypeError(
129190
f"If passing a list to `data` all elements must be an "
130191
f"array-like type representing an n-dimensional image"
131192
)
132193

133-
elif is_arraylike(data):
134-
self.data = [data]
135-
self.ndim = self.data[0].ndim
194+
elif _is_arraylike(data):
195+
self._data = [data]
196+
self._ndim = self.data[0].ndim
136197

137-
self.plot_type = "single"
198+
self._plot_type = "single"
138199
else:
139200
raise TypeError(
140201
f"`data` must be an array-like type representing an n-dimensional image "
@@ -143,12 +204,12 @@ def __init__(
143204

144205
# default dims order if not passed
145206
if dims_order is None:
146-
self.dims_order: List[str] = [DEFAULT_DIMS_ORDER[self.ndim]] * len(self.data)
207+
self._dims_order: List[str] = [DEFAULT_DIMS_ORDER[self.ndim]] * len(self.data)
147208

148209
elif isinstance(dims_order, str):
149-
self.dims_order: List[str] = [dims_order] * len(self.data)
210+
self._dims_order: List[str] = [dims_order] * len(self.data)
150211
elif isinstance(dims_order, dict):
151-
self.dims_order: List[str] = [DEFAULT_DIMS_ORDER[self.ndim]] * len(self.data)
212+
self._dims_order: List[str] = [DEFAULT_DIMS_ORDER[self.ndim]] * len(self.data)
152213

153214
# dict of {array: dims_order_str}
154215
for array in list(dims_order.keys()):
@@ -208,7 +269,7 @@ def __init__(
208269
f"`dims_order` for all arrays must be identical if passing in a <int> `slider_dims` argument. "
209270
f"Pass in a <str> argument if the `dims_order` are different for each array."
210271
)
211-
self.slider_dims: List[str] = [self.dims_order[0][slider_dims]]
272+
self._slider_dims: List[str] = [self.dims_order[0][slider_dims]]
212273

213274
# if dimension specified by str
214275
elif isinstance(slider_dims, str):
@@ -217,11 +278,11 @@ def __init__(
217278
f"if `slider_dims` is a <str>, it must be a character found in `dims_order`. "
218279
f"Your `dims_order` characters are: {set(self.dims_order[0])}."
219280
)
220-
self.slider_dims: List[str] = [slider_dims]
281+
self._slider_dims: List[str] = [slider_dims]
221282

222283
# multiple sliders, one for each dimension
223284
elif isinstance(slider_dims, list):
224-
self.slider_dims: List[str] = list()
285+
self._slider_dims: List[str] = list()
225286

226287
# make sure slice_avg and frame_apply are dicts if multiple sliders are desired
227288
if (not isinstance(slice_avg, dict)) and (slice_avg is not None):
@@ -265,73 +326,73 @@ def __init__(
265326
self._slice_avg = None
266327
self.slice_avg = slice_avg
267328

268-
self.sliders = list()
269-
self.vertical_sliders = list()
270-
self.horizontal_sliders = list()
329+
self._sliders: List[IntSlider] = list()
330+
self._vertical_sliders = list()
331+
self._horizontal_sliders = list()
271332

272333
# current_index stores {dimension_index: slice_index} for every dimension
273-
self.current_index: Dict[str, int] = {sax: 0 for sax in self.slider_dims}
334+
self._current_index: Dict[str, int] = {sax: 0 for sax in self.slider_dims}
274335

275336
# get max bound for all data arrays for all dimensions
276-
self.dims_max_bounds: Dict[str, int] = {k: np.inf for k in self.slider_dims}
277-
for _dim in list(self.dims_max_bounds.keys()):
337+
self._dims_max_bounds: Dict[str, int] = {k: np.inf for k in self.slider_dims}
338+
for _dim in list(self._dims_max_bounds.keys()):
278339
for array, order in zip(self.data, self.dims_order):
279-
self.dims_max_bounds[_dim] = min(self.dims_max_bounds[_dim], array.shape[order.index(_dim)])
340+
self._dims_max_bounds[_dim] = min(self._dims_max_bounds[_dim], array.shape[order.index(_dim)])
280341

281-
if self.plot_type == "single":
282-
self.plot: Plot = Plot()
342+
if self._plot_type == "single":
343+
self._plot: Plot = Plot()
283344

284345
if slice_avg is not None:
285346
pass
286347

287-
frame = self.get_2d_slice(self.data[0], slice_indices=self.current_index)
348+
frame = self._get_2d_slice(self.data[0], slice_indices=self._current_index)
288349

289350
self.image_graphics: List[Image] = [self.plot.image(data=frame, **kwargs)]
290351

291-
elif self.plot_type == "grid":
292-
self.plot: GridPlot = GridPlot(shape=grid_shape, controllers="sync")
352+
elif self._plot_type == "grid":
353+
self._plot: GridPlot = GridPlot(shape=grid_shape, controllers="sync")
293354

294355
self.image_graphics = list()
295356
for d, subplot in zip(self.data, self.plot):
296-
frame = self.get_2d_slice(d, slice_indices=self.current_index)
357+
frame = self._get_2d_slice(d, slice_indices=self._current_index)
297358
ig = Image(frame, **kwargs)
298359
subplot.add_graphic(ig)
299360
self.image_graphics.append(ig)
300361

301-
self.plot.renderer.add_event_handler(self.set_slider_layout, "resize")
362+
self.plot.renderer.add_event_handler(self._set_slider_layout, "resize")
302363

303364
for sdm in self.slider_dims:
304365
if sdm == "z":
305-
# TODO: once ipywidgets plays nicely with HBox and jupyter-rfb can use vertical
366+
# TODO: once ipywidgets plays nicely with HBox and jupyter-rfb, use vertical
306367
# orientation = "vertical"
307368
orientation = "horizontal"
308369
else:
309370
orientation = "horizontal"
310371

311372
slider = IntSlider(
312373
min=0,
313-
max=self.dims_max_bounds[sdm] - 1,
374+
max=self._dims_max_bounds[sdm] - 1,
314375
step=1,
315376
value=0,
316377
description=f"dimension: {sdm}",
317378
orientation=orientation
318379
)
319380

320381
slider.observe(
321-
partial(self.slider_value_changed, sdm),
382+
partial(self._slider_value_changed, sdm),
322383
names="value"
323384
)
324385

325-
self.sliders.append(slider)
386+
self._sliders.append(slider)
326387
if orientation == "horizontal":
327-
self.horizontal_sliders.append(slider)
388+
self._horizontal_sliders.append(slider)
328389
elif orientation == "vertical":
329-
self.vertical_sliders.append(slider)
390+
self._vertical_sliders.append(slider)
330391

331392
# TODO: So just stack everything vertically for now
332393
self.widget = VBox([
333394
self.plot.canvas,
334-
*self.sliders
395+
*self._sliders
335396
])
336397

337398
# TODO: there is currently an issue with ipywidgets or jupyter-rfb and HBox doesn't work with RFB canvas
@@ -391,7 +452,7 @@ def slice_avg(self, sa: Union[int, Dict[str, int]]):
391452
f"You have passed a {type(sa)}. See the docstring."
392453
)
393454

394-
def get_2d_slice(
455+
def _get_2d_slice(
395456
self,
396457
array: np.ndarray,
397458
slice_indices: dict[Union[int, str], int]
@@ -485,30 +546,34 @@ def _process_dim_index(self, data_ix, dim, indices_dim):
485546

486547
hw = int((sa - 1) / 2) # half-window size
487548
# get the max bound for that dimension
488-
max_bound = self.dims_max_bounds[dim_str]
549+
max_bound = self._dims_max_bounds[dim_str]
489550
indices_dim = range(max(0, ix - hw), min(max_bound, ix + hw))
490551
return indices_dim
491552

492-
def slider_value_changed(
553+
def _slider_value_changed(
493554
self,
494-
dimension: int,
555+
dimension: str,
495556
change: dict
496557
):
497-
self.current_index[dimension] = change["new"]
558+
self.current_index = {dimension: change["new"]}
498559

499-
for ig, data in zip(self.image_graphics, self.data):
500-
frame = self.get_2d_slice(data, self.current_index)
501-
ig.update_data(frame)
502-
503-
def set_slider_layout(self, *args):
560+
def _set_slider_layout(self, *args):
504561
w, h = self.plot.renderer.logical_size
505-
for hs in self.horizontal_sliders:
562+
for hs in self._horizontal_sliders:
506563
hs.layout = Layout(width=f"{w}px")
507564

508-
for vs in self.vertical_sliders:
565+
for vs in self._vertical_sliders:
509566
vs.layout = Layout(height=f"{h}px")
510567

511568
def show(self):
569+
"""
570+
Show the widget
571+
572+
Returns
573+
-------
574+
VBox
575+
``ipywidgets.VBox`` stacking the plotter and sliders in a vertical layout
576+
"""
512577
# start render loop
513578
self.plot.show()
514579

0 commit comments

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