Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 718c98c

Browse files
optimized array subsampling (#721)
* add 4D case with a single z-plane * add subsample_array function * use new subsample_array function * remove redundant target_elements from subsample * import utils from __all__ * rename subsample vars using standard numpy names * subsample max_items -> max_size * largest bytesize -> largest array size, lowercase errrrything * use subsample_array in quick_min_max * make 1e6 int * update png from artifact (imgui-screenshots) * fix subsample array * bring back original image_widget_grid * Update functions.py * revert image_widget_grid from regenerate * hopefully the correct screenshot * black * replace iw-zfish-grid-qreplace nb-iw-zfish-grid-init-mw5 to see * replace nb-image-widget screenshots * force git to refresh * nb-iw from regen screenshots * plzplzplzplzplz work --------- Co-authored-by: Kushal Kolar <[email protected]>
1 parent be8e0c8 commit 718c98c

File tree

32 files changed

+129
-87
lines changed

32 files changed

+129
-87
lines changed
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 3 additions & 2 deletions
Loading

fastplotlib/tools/_histogram_lut.py

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import pygfx
77

8+
from ..utils import subsample_array
89
from ..graphics import LineGraphic, ImageGraphic, TextGraphic
910
from ..graphics.utils import pause_events
1011
from ..graphics._base import Graphic
@@ -193,28 +194,10 @@ def _fpl_add_plot_area_hook(self, plot_area):
193194
self._plot_area.controller.enabled = True
194195

195196
def _calculate_histogram(self, data):
196-
if data.ndim > 2:
197-
# subsample to max of 500 x 100 x 100,
198-
# np.histogram takes ~30ms with this size on a 8 core Ryzen laptop
199-
# dim0 is usually time, allow max of 500 timepoints
200-
ss0 = max(1, int(data.shape[0] / 500)) # max to prevent step = 0
201-
# allow max of 100 for x and y if ndim > 2
202-
ss1 = max(1, int(data.shape[1] / 100))
203-
ss2 = max(1, int(data.shape[2] / 100))
204197

205-
data_ss = data[::ss0, ::ss1, ::ss2]
206-
207-
hist, edges = np.histogram(data_ss, bins=self._nbins)
208-
209-
else:
210-
# allow max of 1000 x 1000
211-
# this takes ~4ms on a 8 core Ryzen laptop
212-
ss0 = max(1, int(data.shape[0] / 1_000))
213-
ss1 = max(1, int(data.shape[1] / 1_000))
214-
215-
data_ss = data[::ss0, ::ss1]
216-
217-
hist, edges = np.histogram(data_ss, bins=self._nbins)
198+
# get a subsampled view of this array
199+
data_ss = subsample_array(data, max_size=int(1e6)) # 1e6 is default
200+
hist, edges = np.histogram(data_ss, bins=self._nbins)
218201

219202
# used if data ptp <= 10 because event things get weird
220203
# with tiny world objects due to floating point error

fastplotlib/utils/functions.py

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ def make_colors_dict(labels: Sequence, cmap: str, **kwargs) -> OrderedDict:
267267
return OrderedDict(zip(labels, colors))
268268

269269

270-
def quick_min_max(data: np.ndarray) -> tuple[float, float]:
270+
def quick_min_max(data: np.ndarray, max_size=1e6) -> tuple[float, float]:
271271
"""
272272
Adapted from pyqtgraph.ImageView.
273273
Estimate the min/max values of *data* by subsampling.
@@ -276,6 +276,9 @@ def quick_min_max(data: np.ndarray) -> tuple[float, float]:
276276
----------
277277
data: np.ndarray or array-like with `min` and `max` attributes
278278
279+
max_size : int, optional
280+
largest array size allowed in the subsampled array. Default is 1e6.
281+
279282
Returns
280283
-------
281284
(float, float)
@@ -289,11 +292,7 @@ def quick_min_max(data: np.ndarray) -> tuple[float, float]:
289292
):
290293
return data.min, data.max
291294

292-
while np.prod(data.shape) > 1e6:
293-
ax = np.argmax(data.shape)
294-
sl = [slice(None)] * data.ndim
295-
sl[ax] = slice(None, None, 2)
296-
data = data[tuple(sl)]
295+
data = subsample_array(data, max_size=max_size)
297296

298297
return float(np.nanmin(data)), float(np.nanmax(data))
299298

@@ -405,3 +404,62 @@ def parse_cmap_values(
405404
colors = np.vstack([colormap[val] for val in norm_cmap_values])
406405

407406
return colors
407+
408+
409+
def subsample_array(arr: np.ndarray, max_size: int = 1e6):
410+
"""
411+
Subsamples an input array while preserving its relative dimensional proportions.
412+
413+
The dimensions (shape) of the array can be represented as:
414+
415+
.. math::
416+
417+
[d_1, d_2, \\dots d_n]
418+
419+
The product of the dimensions can be represented as:
420+
421+
.. math::
422+
423+
\\prod_{i=1}^{n} d_i
424+
425+
To find the factor ``f`` by which to divide the size of each dimension in order to
426+
get max_size ``s`` we must solve for ``f`` in the following expression:
427+
428+
.. math::
429+
430+
\\prod_{i=1}^{n} \\frac{d_i}{\\mathbf{f}} = \\mathbf{s}
431+
432+
The solution for ``f`` is is simply the nth root of the product of the dims divided by the max_size
433+
where n is the number of dimensions
434+
435+
.. math::
436+
437+
\\mathbf{f} = \\sqrt[n]{\\frac{\\prod_{i=1}^{n} d_i}{\\mathbf{s}}}
438+
439+
Parameters
440+
----------
441+
arr: np.ndarray
442+
input array of any dimensionality to be subsampled.
443+
444+
max_size: int, default 1e6
445+
maximum number of elements in subsampled array
446+
447+
Returns
448+
-------
449+
np.ndarray
450+
subsample of the input array
451+
"""
452+
if np.prod(arr.shape) <= max_size:
453+
return arr # no need to subsample if already below the threshold
454+
455+
# get factor by which to divide all dims
456+
f = np.power((np.prod(arr.shape) / max_size), 1.0 / arr.ndim)
457+
458+
# new shape for subsampled array
459+
ns = np.floor(np.array(arr.shape) / f).clip(min=1)
460+
461+
# get the step size for the slices
462+
slices = tuple(
463+
slice(None, None, int(s)) for s in np.floor(arr.shape / ns).astype(int)
464+
)
465+
return np.asarray(arr[slices])

0 commit comments

Comments
 (0)