phasorpy 0.3__cp312-cp312-win_amd64.whl → 0.5__cp312-cp312-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
phasorpy/_phasorpy.pyx CHANGED
@@ -4,15 +4,10 @@
4
4
  # cython: wraparound = False
5
5
  # cython: cdivision = True
6
6
  # cython: nonecheck = False
7
+ # cython: freethreading_compatible = True
7
8
 
8
9
  """Cython implementation of low-level functions for the PhasorPy library."""
9
10
 
10
- # TODO: replace short with unsigned char when Cython supports it
11
- # https://github.com/cython/cython/pull/6196#issuecomment-2209509572
12
-
13
- # TODO: use fused return types for functions returning more than two items
14
- # https://github.com/cython/cython/issues/6328
15
-
16
11
  cimport cython
17
12
 
18
13
  from cython.parallel import parallel, prange
@@ -49,6 +44,12 @@ ctypedef fused float_t:
49
44
  float
50
45
  double
51
46
 
47
+ ctypedef fused uint_t:
48
+ uint8_t
49
+ uint16_t
50
+ uint32_t
51
+ uint64_t
52
+
52
53
  ctypedef fused signal_t:
53
54
  uint8_t
54
55
  uint16_t
@@ -445,7 +446,7 @@ cdef (double, double) _phasor_from_fret_donor(
445
446
  double omega,
446
447
  double donor_lifetime,
447
448
  double fret_efficiency,
448
- double donor_freting,
449
+ double donor_fretting,
449
450
  double donor_background,
450
451
  double background_real,
451
452
  double background_imag,
@@ -465,16 +466,16 @@ cdef (double, double) _phasor_from_fret_donor(
465
466
  elif fret_efficiency > 1.0:
466
467
  fret_efficiency = 1.0
467
468
 
468
- if donor_freting < 0.0:
469
- donor_freting = 0.0
470
- elif donor_freting > 1.0:
471
- donor_freting = 1.0
469
+ if donor_fretting < 0.0:
470
+ donor_fretting = 0.0
471
+ elif donor_fretting > 1.0:
472
+ donor_fretting = 1.0
472
473
 
473
474
  if donor_background < 0.0:
474
475
  donor_background = 0.0
475
476
 
476
- f_pure = 1.0 - donor_freting
477
- f_quenched = (1.0 - fret_efficiency) * donor_freting
477
+ f_pure = 1.0 - donor_fretting
478
+ f_quenched = (1.0 - fret_efficiency) * donor_fretting
478
479
  sum = f_pure + f_quenched + donor_background
479
480
  if sum < 1e-9:
480
481
  # no signal in donor channel
@@ -510,7 +511,7 @@ cdef (double, double) _phasor_from_fret_acceptor(
510
511
  double donor_lifetime,
511
512
  double acceptor_lifetime,
512
513
  double fret_efficiency,
513
- double donor_freting,
514
+ double donor_fretting,
514
515
  double donor_bleedthrough,
515
516
  double acceptor_bleedthrough,
516
517
  double acceptor_background,
@@ -535,10 +536,10 @@ cdef (double, double) _phasor_from_fret_acceptor(
535
536
  elif fret_efficiency > 1.0:
536
537
  fret_efficiency = 1.0
537
538
 
538
- if donor_freting < 0.0:
539
- donor_freting = 0.0
540
- elif donor_freting > 1.0:
541
- donor_freting = 1.0
539
+ if donor_fretting < 0.0:
540
+ donor_fretting = 0.0
541
+ elif donor_fretting > 1.0:
542
+ donor_fretting = 1.0
542
543
 
543
544
  if donor_bleedthrough < 0.0:
544
545
  donor_bleedthrough = 0.0
@@ -569,7 +570,7 @@ cdef (double, double) _phasor_from_fret_acceptor(
569
570
  quenched_imag,
570
571
  1.0,
571
572
  1.0 - fret_efficiency,
572
- 1.0 - donor_freting
573
+ 1.0 - donor_fretting
573
574
  )
574
575
 
575
576
  # phasor of acceptor at frequency
@@ -591,8 +592,8 @@ cdef (double, double) _phasor_from_fret_acceptor(
591
592
  sensitized_imag = mod * sin(phi)
592
593
 
593
594
  # weighted average
594
- f_donor = donor_bleedthrough * (1.0 - donor_freting * fret_efficiency)
595
- f_acceptor = donor_freting * fret_efficiency
595
+ f_donor = donor_bleedthrough * (1.0 - donor_fretting * fret_efficiency)
596
+ f_acceptor = donor_fretting * fret_efficiency
596
597
  sum = f_donor + f_acceptor + acceptor_bleedthrough + acceptor_background
597
598
  if sum < 1e-9:
598
599
  # no signal in acceptor channel
@@ -913,7 +914,7 @@ cdef (float_t, float_t) _phasor_at_harmonic(
913
914
  int harmonic,
914
915
  int other_harmonic,
915
916
  ) noexcept nogil:
916
- """Return phasor coordinates on semicircle at other harmonic."""
917
+ """Return phasor coordinates on universal semicircle at other harmonic."""
917
918
  if isnan(real):
918
919
  return <float_t> NAN, <float_t> NAN
919
920
 
@@ -976,7 +977,7 @@ cdef (float_t, float_t) _phasor_divide(
976
977
 
977
978
 
978
979
  @cython.ufunc
979
- cdef short _is_inside_range(
980
+ cdef unsigned char _is_inside_range(
980
981
  float_t x, # point
981
982
  float_t y,
982
983
  float_t xmin, # x range
@@ -996,7 +997,7 @@ cdef short _is_inside_range(
996
997
 
997
998
 
998
999
  @cython.ufunc
999
- cdef short _is_inside_rectangle(
1000
+ cdef unsigned char _is_inside_rectangle(
1000
1001
  float_t x, # point
1001
1002
  float_t y,
1002
1003
  float_t x0, # segment start
@@ -1038,7 +1039,7 @@ cdef short _is_inside_rectangle(
1038
1039
 
1039
1040
 
1040
1041
  @cython.ufunc
1041
- cdef short _is_inside_polar_rectangle(
1042
+ cdef unsigned char _is_inside_polar_rectangle(
1042
1043
  float_t x, # point
1043
1044
  float_t y,
1044
1045
  float_t angle_min, # phase, -pi to pi
@@ -1048,7 +1049,7 @@ cdef short _is_inside_polar_rectangle(
1048
1049
  ) noexcept nogil:
1049
1050
  """Return whether point is inside polar rectangle.
1050
1051
 
1051
- Angles should be in range -pi to pi, else performance is degraded.
1052
+ Angles should be in range [-pi, pi], else performance is degraded.
1052
1053
 
1053
1054
  """
1054
1055
  cdef:
@@ -1077,7 +1078,7 @@ cdef short _is_inside_polar_rectangle(
1077
1078
 
1078
1079
 
1079
1080
  @cython.ufunc
1080
- cdef short _is_inside_circle(
1081
+ cdef unsigned char _is_inside_circle(
1081
1082
  float_t x, # point
1082
1083
  float_t y,
1083
1084
  float_t x0, # circle center
@@ -1094,7 +1095,7 @@ cdef short _is_inside_circle(
1094
1095
 
1095
1096
 
1096
1097
  @cython.ufunc
1097
- cdef short _is_inside_ellipse(
1098
+ cdef unsigned char _is_inside_ellipse(
1098
1099
  float_t x, # point
1099
1100
  float_t y,
1100
1101
  float_t x0, # ellipse center
@@ -1129,7 +1130,7 @@ cdef short _is_inside_ellipse(
1129
1130
 
1130
1131
 
1131
1132
  @cython.ufunc
1132
- cdef short _is_inside_ellipse_(
1133
+ cdef unsigned char _is_inside_ellipse_(
1133
1134
  float_t x, # point
1134
1135
  float_t y,
1135
1136
  float_t x0, # ellipse center
@@ -1158,7 +1159,7 @@ cdef short _is_inside_ellipse_(
1158
1159
 
1159
1160
 
1160
1161
  @cython.ufunc
1161
- cdef short _is_inside_stadium(
1162
+ cdef unsigned char _is_inside_stadium(
1162
1163
  float_t x, # point
1163
1164
  float_t y,
1164
1165
  float_t x0, # line start
@@ -1204,7 +1205,7 @@ _is_near_segment = _is_inside_stadium
1204
1205
 
1205
1206
 
1206
1207
  @cython.ufunc
1207
- cdef short _is_near_line(
1208
+ cdef unsigned char _is_near_line(
1208
1209
  float_t x, # point
1209
1210
  float_t y,
1210
1211
  float_t x0, # line start
@@ -1470,7 +1471,7 @@ cdef float_t _distance_from_line(
1470
1471
 
1471
1472
 
1472
1473
  @cython.ufunc
1473
- cdef (double, double, double) _segment_direction_and_length(
1474
+ cdef (float_t, float_t, float_t) _segment_direction_and_length(
1474
1475
  float_t x0, # segment start
1475
1476
  float_t y0,
1476
1477
  float_t x1, # segment end
@@ -1494,7 +1495,7 @@ cdef (double, double, double) _segment_direction_and_length(
1494
1495
 
1495
1496
 
1496
1497
  @cython.ufunc
1497
- cdef (double, double, double, double) _intersection_circle_circle(
1498
+ cdef (float_t, float_t, float_t, float_t) _intersection_circle_circle(
1498
1499
  float_t x0, # circle 0
1499
1500
  float_t y0,
1500
1501
  float_t r0,
@@ -1532,15 +1533,15 @@ cdef (double, double, double, double) _intersection_circle_circle(
1532
1533
  hd = sqrt(dd) / dr
1533
1534
  ld = ll / dr
1534
1535
  return (
1535
- ld * dx + hd * dy + x0,
1536
- ld * dy - hd * dx + y0,
1537
- ld * dx - hd * dy + x0,
1538
- ld * dy + hd * dx + y0,
1536
+ <float_t> (ld * dx + hd * dy + x0),
1537
+ <float_t> (ld * dy - hd * dx + y0),
1538
+ <float_t> (ld * dx - hd * dy + x0),
1539
+ <float_t> (ld * dy + hd * dx + y0),
1539
1540
  )
1540
1541
 
1541
1542
 
1542
1543
  @cython.ufunc
1543
- cdef (double, double, double, double) _intersection_circle_line(
1544
+ cdef (float_t, float_t, float_t, float_t) _intersection_circle_line(
1544
1545
  float_t x, # circle
1545
1546
  float_t y,
1546
1547
  float_t r,
@@ -1575,10 +1576,10 @@ cdef (double, double, double, double) _intersection_circle_line(
1575
1576
  return NAN, NAN, NAN, NAN
1576
1577
  rdd = sqrt(rdd)
1577
1578
  return (
1578
- x + (dd * dy + copysign(1.0, dy) * dx * rdd) / dr,
1579
- y + (-dd * dx + fabs(dy) * rdd) / dr,
1580
- x + (dd * dy - copysign(1.0, dy) * dx * rdd) / dr,
1581
- y + (-dd * dx - fabs(dy) * rdd) / dr,
1579
+ x + <float_t> ((dd * dy + copysign(1.0, dy) * dx * rdd) / dr),
1580
+ y + <float_t> ((-dd * dx + fabs(dy) * rdd) / dr),
1581
+ x + <float_t> ((dd * dy - copysign(1.0, dy) * dx * rdd) / dr),
1582
+ y + <float_t> ((-dd * dx - fabs(dy) * rdd) / dr),
1582
1583
  )
1583
1584
 
1584
1585
 
@@ -1659,7 +1660,7 @@ cdef float_t _blend_lighten(
1659
1660
 
1660
1661
 
1661
1662
  @cython.ufunc
1662
- cdef (double, double, double) _phasor_threshold_open(
1663
+ cdef (float_t, float_t, float_t) _phasor_threshold_open(
1663
1664
  float_t mean,
1664
1665
  float_t real,
1665
1666
  float_t imag,
@@ -1721,7 +1722,7 @@ cdef (double, double, double) _phasor_threshold_open(
1721
1722
 
1722
1723
 
1723
1724
  @cython.ufunc
1724
- cdef (double, double, double) _phasor_threshold_closed(
1725
+ cdef (float_t, float_t, float_t) _phasor_threshold_closed(
1725
1726
  float_t mean,
1726
1727
  float_t real,
1727
1728
  float_t imag,
@@ -1783,7 +1784,7 @@ cdef (double, double, double) _phasor_threshold_closed(
1783
1784
 
1784
1785
 
1785
1786
  @cython.ufunc
1786
- cdef (double, double, double) _phasor_threshold_mean_open(
1787
+ cdef (float_t, float_t, float_t) _phasor_threshold_mean_open(
1787
1788
  float_t mean,
1788
1789
  float_t real,
1789
1790
  float_t imag,
@@ -1803,7 +1804,7 @@ cdef (double, double, double) _phasor_threshold_mean_open(
1803
1804
 
1804
1805
 
1805
1806
  @cython.ufunc
1806
- cdef (double, double, double) _phasor_threshold_mean_closed(
1807
+ cdef (float_t, float_t, float_t) _phasor_threshold_mean_closed(
1807
1808
  float_t mean,
1808
1809
  float_t real,
1809
1810
  float_t imag,
@@ -1823,7 +1824,7 @@ cdef (double, double, double) _phasor_threshold_mean_closed(
1823
1824
 
1824
1825
 
1825
1826
  @cython.ufunc
1826
- cdef (double, double, double) _phasor_threshold_nan(
1827
+ cdef (float_t, float_t, float_t) _phasor_threshold_nan(
1827
1828
  float_t mean,
1828
1829
  float_t real,
1829
1830
  float_t imag,
@@ -2159,3 +2160,74 @@ def _median_filter_2d(
2159
2160
  image[i, j] = filtered_image[i, j]
2160
2161
 
2161
2162
  free(kernel)
2163
+
2164
+
2165
+ ###############################################################################
2166
+ # Decoder functions
2167
+
2168
+
2169
+ @cython.boundscheck(True)
2170
+ def _flimlabs_signal(
2171
+ uint_t[:, :, ::] signal, # channel, pixel, bin
2172
+ list data, # list[list[list[[int, int]]]]
2173
+ ssize_t channel = -1 # -1 == None
2174
+ ):
2175
+ """Return TCSPC histogram image from FLIM LABS JSON intensity data."""
2176
+ cdef:
2177
+ uint_t[::] signal_
2178
+ list channels, pixels
2179
+ ssize_t c, i, h, count
2180
+
2181
+ if channel < 0:
2182
+ c = 0
2183
+ for channels in data:
2184
+ i = 0
2185
+ for pixels in channels:
2186
+ signal_ = signal[c, i]
2187
+ for h, count in pixels:
2188
+ signal_[h] = <uint_t> count
2189
+ i += 1
2190
+ c += 1
2191
+ else:
2192
+ i = 0
2193
+ for pixels in data[channel]:
2194
+ signal_ = signal[0, i]
2195
+ for h, count in pixels:
2196
+ signal_[h] = <uint_t> count
2197
+ i += 1
2198
+
2199
+
2200
+ @cython.boundscheck(True)
2201
+ def _flimlabs_mean(
2202
+ float_t[:, ::] mean, # channel, pixel
2203
+ list data, # list[list[list[[int, int]]]]
2204
+ ssize_t channel = -1 # -1 == None
2205
+ ):
2206
+ """Return mean intensity image from FLIM LABS JSON intensity data."""
2207
+ cdef:
2208
+ float_t[::] mean_
2209
+ list channels, pixels
2210
+ ssize_t c, i, h, count
2211
+ double sum
2212
+
2213
+ if channel < 0:
2214
+ c = 0
2215
+ for channels in data:
2216
+ mean_ = mean[c]
2217
+ i = 0
2218
+ for pixels in channels:
2219
+ sum = 0.0
2220
+ for h, count in pixels:
2221
+ sum += <double> count
2222
+ mean_[i] = <float_t> (sum / 256.0)
2223
+ i += 1
2224
+ c += 1
2225
+ else:
2226
+ i = 0
2227
+ mean_ = mean[0]
2228
+ for pixels in data[channel]:
2229
+ sum = 0.0
2230
+ for h, count in pixels:
2231
+ sum += <double> count
2232
+ mean_[i] = <float_t> (sum / 256.0)
2233
+ i += 1
phasorpy/_utils.py CHANGED
@@ -1,28 +1,30 @@
1
- """Private auxiliary and convenience functions.
2
-
3
- """
1
+ """Private auxiliary and convenience functions."""
4
2
 
5
3
  from __future__ import annotations
6
4
 
7
- __all__: list[str] = [
5
+ __all__ = [
8
6
  'chunk_iter',
9
7
  'dilate_coordinates',
10
8
  'kwargs_notnone',
11
9
  'parse_harmonic',
12
10
  'parse_kwargs',
11
+ 'parse_signal_axis',
12
+ 'parse_skip_axis',
13
13
  'phasor_from_polar_scalar',
14
14
  'phasor_to_polar_scalar',
15
15
  'scale_matrix',
16
+ 'set_module',
16
17
  'sort_coordinates',
17
18
  'update_kwargs',
18
19
  ]
19
20
 
20
21
  import math
21
22
  import numbers
23
+ from collections.abc import Sequence
22
24
  from typing import TYPE_CHECKING
23
25
 
24
26
  if TYPE_CHECKING:
25
- from ._typing import Any, Sequence, ArrayLike, Literal, NDArray, Iterator
27
+ from ._typing import Any, ArrayLike, Literal, NDArray, Iterator
26
28
 
27
29
  import numpy
28
30
 
@@ -247,6 +249,131 @@ def phasor_from_polar_scalar(
247
249
  return real, imag
248
250
 
249
251
 
252
+ def parse_signal_axis(
253
+ signal: ArrayLike,
254
+ /,
255
+ axis: int | str | None = None,
256
+ ) -> tuple[int, str]:
257
+ """Return axis over which phasor coordinates are computed.
258
+
259
+ The axis parameter is not validated against the signal shape.
260
+
261
+ Parameters
262
+ ----------
263
+ signal : array_like
264
+ Image stack.
265
+ axis : int or str, optional
266
+ Axis over which phasor coordinates are computed.
267
+ By default, the 'H' or 'C' axes if `signal` contains such
268
+ dimension names, else the last axis (-1).
269
+
270
+ Returns
271
+ -------
272
+ axis : int
273
+ Axis over which phasor coordinates are computed.
274
+ axis_label : str
275
+ Axis label from `signal.dims` if any.
276
+
277
+ Raises
278
+ ------
279
+ ValueError
280
+ Axis not found in signal.dims or invalid for signal type.
281
+
282
+ Examples
283
+ --------
284
+ >>> parse_signal_axis([])
285
+ (-1, '')
286
+ >>> parse_signal_axis([], 1)
287
+ (1, '')
288
+ >>> class DataArray:
289
+ ... dims = ('C', 'H', 'Y', 'X')
290
+ ...
291
+ >>> parse_signal_axis(DataArray())
292
+ (1, 'H')
293
+ >>> parse_signal_axis(DataArray(), 'C')
294
+ (0, 'C')
295
+ >>> parse_signal_axis(DataArray(), 1)
296
+ (1, 'H')
297
+
298
+ """
299
+ if hasattr(signal, 'dims'):
300
+ assert isinstance(signal.dims, tuple)
301
+ if axis is None:
302
+ for ax in 'HC':
303
+ if ax in signal.dims:
304
+ return signal.dims.index(ax), ax
305
+ return -1, signal.dims[-1]
306
+ if isinstance(axis, int):
307
+ return axis, signal.dims[axis]
308
+ if axis in signal.dims:
309
+ return signal.dims.index(axis), axis
310
+ raise ValueError(f'{axis=} not found in {signal.dims}')
311
+ if axis is None:
312
+ return -1, ''
313
+ if isinstance(axis, int):
314
+ return axis, ''
315
+ raise ValueError(f'{axis=} not valid for {type(signal)=}')
316
+
317
+
318
+ def parse_skip_axis(
319
+ skip_axis: int | Sequence[int] | None,
320
+ /,
321
+ ndim: int,
322
+ prepend_axis: bool = False,
323
+ ) -> tuple[tuple[int, ...], tuple[int, ...]]:
324
+ """Return axes to skip and not to skip.
325
+
326
+ This helper function is used to validate and parse `skip_axis`
327
+ parameters.
328
+
329
+ Parameters
330
+ ----------
331
+ skip_axis : int or sequence of int, optional
332
+ Axes to skip. If None, no axes are skipped.
333
+ ndim : int
334
+ Dimensionality of array in which to skip axes.
335
+ prepend_axis : bool, optional
336
+ Prepend one dimension and include in `skip_axis`.
337
+
338
+ Returns
339
+ -------
340
+ skip_axis : tuple of int
341
+ Ordered, positive values of `skip_axis`.
342
+ other_axis : tuple of int
343
+ Axes indices not included in `skip_axis`.
344
+
345
+ Raises
346
+ ------
347
+ IndexError
348
+ If any `skip_axis` value is out of bounds of `ndim`.
349
+
350
+ Examples
351
+ --------
352
+ >>> parse_skip_axis((1, -2), 5)
353
+ ((1, 3), (0, 2, 4))
354
+
355
+ >>> parse_skip_axis((1, -2), 5, True)
356
+ ((0, 2, 4), (1, 3, 5))
357
+
358
+ """
359
+ if ndim < 0:
360
+ raise ValueError(f'invalid {ndim=}')
361
+ if skip_axis is None:
362
+ if prepend_axis:
363
+ return (0,), tuple(range(1, ndim + 1))
364
+ return (), tuple(range(ndim))
365
+ if not isinstance(skip_axis, Sequence):
366
+ skip_axis = (skip_axis,)
367
+ if any(i >= ndim or i < -ndim for i in skip_axis):
368
+ raise IndexError(f'skip_axis={skip_axis} out of range for {ndim=}')
369
+ skip_axis = sorted(int(i % ndim) for i in skip_axis)
370
+ if prepend_axis:
371
+ skip_axis = [0] + [i + 1 for i in skip_axis]
372
+ ndim += 1
373
+ other_axis = tuple(i for i in range(ndim) if i not in skip_axis)
374
+ return tuple(skip_axis), other_axis
375
+
376
+
250
377
  def parse_harmonic(
251
378
  harmonic: int | Sequence[int] | Literal['all'] | str | None,
252
379
  harmonic_max: int | None = None,
@@ -259,7 +386,7 @@ def parse_harmonic(
259
386
 
260
387
  Parameters
261
388
  ----------
262
- harmonic : int, list of int, 'all', or None
389
+ harmonic : int, sequence of int, 'all', or None
263
390
  Harmonic parameter to parse.
264
391
  harmonic_max : int, optional
265
392
  Maximum value allowed in `hamonic`. Must be one or greater.
@@ -278,7 +405,7 @@ def parse_harmonic(
278
405
  Raises
279
406
  ------
280
407
  IndexError
281
- Any element is out of range `[1..harmonic_max]`.
408
+ Any element is out of range `[1, harmonic_max]`.
282
409
  ValueError
283
410
  Elements are not unique.
284
411
  Harmonic is empty.
@@ -299,7 +426,7 @@ def parse_harmonic(
299
426
  if harmonic < 1 or (
300
427
  harmonic_max is not None and harmonic > harmonic_max
301
428
  ):
302
- raise IndexError(f'{harmonic=} out of range [1..{harmonic_max}]')
429
+ raise IndexError(f'{harmonic=} out of range [1, {harmonic_max}]')
303
430
  return [int(harmonic)], False
304
431
 
305
432
  if isinstance(harmonic, str):
@@ -311,7 +438,7 @@ def parse_harmonic(
311
438
  return list(range(1, harmonic_max + 1)), True
312
439
  raise ValueError(f'{harmonic=!r} is not a valid harmonic')
313
440
 
314
- h = numpy.atleast_1d(numpy.asarray(harmonic))
441
+ h = numpy.atleast_1d(harmonic)
315
442
  if h.size == 0:
316
443
  raise ValueError(f'{harmonic=} is empty')
317
444
  if h.dtype.kind not in 'iu' or h.ndim != 1:
@@ -322,14 +449,14 @@ def parse_harmonic(
322
449
  raise IndexError(f'{harmonic=} element > {harmonic_max}]')
323
450
  if numpy.unique(h).size != h.size:
324
451
  raise ValueError(f'{harmonic=} elements must be unique')
325
- return h.tolist(), True
452
+ return [int(i) for i in harmonic], True
326
453
 
327
454
 
328
455
  def chunk_iter(
329
456
  shape: tuple[int, ...],
330
457
  chunk_shape: tuple[int, ...],
331
458
  /,
332
- axes: str | Sequence[str] | None = None,
459
+ dims: Sequence[str] | None = None,
333
460
  *,
334
461
  pattern: str | None = None,
335
462
  squeeze: bool = False,
@@ -343,11 +470,11 @@ def chunk_iter(
343
470
  Shape of C-order ndarray to chunk.
344
471
  chunk_shape : tuple of int
345
472
  Shape of chunks in the most significant dimensions.
346
- axes : str or sequence of str, optional
473
+ dims : sequence of str, optional
347
474
  Labels for each axis in shape if `pattern` is None.
348
475
  pattern : str, optional
349
476
  String to format chunk indices.
350
- If None, use ``_[{axes[index]}{chunk_index[index]}]`` for each axis.
477
+ If None, use ``_[{dims[index]}{chunk_index[index]}]`` for each axis.
351
478
  squeeze : bool
352
479
  If true, do not include length-1 chunked dimensions in label
353
480
  unless dimensions are part of `chunk_shape`.
@@ -384,11 +511,11 @@ def chunk_iter(
384
511
  ndim = len(shape)
385
512
 
386
513
  sep = '_'
387
- if axes is None:
388
- axes = sep * ndim
514
+ if dims is None:
515
+ dims = sep * ndim
389
516
  sep = ''
390
- elif ndim != len(axes):
391
- raise ValueError(f'{len(shape)=} != {len(axes)=}')
517
+ elif ndim != len(dims):
518
+ raise ValueError(f'{len(shape)=} != {len(dims)=}')
392
519
 
393
520
  if pattern is not None:
394
521
  try:
@@ -406,7 +533,7 @@ def chunk_iter(
406
533
 
407
534
  chunked_shape = []
408
535
  pattern_list = []
409
- for i, (size, chunk_size, ax) in enumerate(zip(shape, chunk_shape, axes)):
536
+ for i, (size, chunk_size, ax) in enumerate(zip(shape, chunk_shape, dims)):
410
537
  if size <= 0:
411
538
  raise ValueError('shape must contain positive sizes')
412
539
  if chunk_size <= 0:
@@ -452,3 +579,23 @@ def chunk_iter(
452
579
  for i in range(ndim)
453
580
  ),
454
581
  )
582
+
583
+
584
+ def set_module(globs: dict[str, Any], /) -> None:
585
+ """Set ``__module__`` attribute for objects in ``__all__``.
586
+
587
+ Parameters
588
+ ----------
589
+ globs : dict
590
+ Module namespace to modify.
591
+
592
+ Examples
593
+ --------
594
+ >>> set_module(globals())
595
+
596
+ """
597
+ name = globs['__name__']
598
+ for item in globs['__all__']:
599
+ obj = globs[item]
600
+ if hasattr(obj, '__module__'):
601
+ obj.__module__ = name
phasorpy/cli.py CHANGED
@@ -8,6 +8,8 @@ Invoke the command line application with::
8
8
 
9
9
  from __future__ import annotations
10
10
 
11
+ __all__: list[str] = []
12
+
11
13
  import os
12
14
  from typing import TYPE_CHECKING
13
15