phasorpy 0.4__cp311-cp311-win_amd64.whl → 0.5__cp311-cp311-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.
phasorpy/phasor.py CHANGED
@@ -59,13 +59,14 @@ The ``phasorpy.phasor`` module provides functions to:
59
59
  - :py:func:`lifetime_fraction_from_amplitude`
60
60
  - :py:func:`lifetime_fraction_to_amplitude`
61
61
 
62
- - calculate phasor coordinates on semicircle at other harmonics:
62
+ - calculate phasor coordinates on universal semicircle at other harmonics:
63
63
 
64
64
  - :py:func:`phasor_at_harmonic`
65
65
 
66
66
  - filter phasor coordinates:
67
67
 
68
68
  - :py:func:`phasor_filter_median`
69
+ - :py:func:`phasor_filter_pawflim`
69
70
  - :py:func:`phasor_threshold`
70
71
 
71
72
  """
@@ -83,6 +84,7 @@ __all__ = [
83
84
  'phasor_center',
84
85
  'phasor_divide',
85
86
  'phasor_filter_median',
87
+ 'phasor_filter_pawflim',
86
88
  'phasor_from_apparent_lifetime',
87
89
  'phasor_from_fret_acceptor',
88
90
  'phasor_from_fret_donor',
@@ -149,7 +151,7 @@ from ._phasorpy import (
149
151
  _polar_from_single_lifetime,
150
152
  _polar_to_apparent_lifetime,
151
153
  )
152
- from ._utils import parse_harmonic, parse_signal_axis
154
+ from ._utils import parse_harmonic, parse_signal_axis, parse_skip_axis
153
155
  from .utils import number_threads
154
156
 
155
157
 
@@ -502,7 +504,8 @@ def phasor_to_signal(
502
504
  else:
503
505
  keepdims = mean.ndim > 0 or real.ndim > 0
504
506
 
505
- mean, real = numpy.atleast_1d(mean, real)
507
+ mean = numpy.asarray(numpy.atleast_1d(mean))
508
+ real = numpy.asarray(numpy.atleast_1d(real))
506
509
 
507
510
  if real.dtype.kind != 'f' or imag.dtype.kind != 'f':
508
511
  raise ValueError(f'{real.dtype=} or {imag.dtype=} not floating point')
@@ -595,7 +598,7 @@ def lifetime_to_signal(
595
598
  or `1` to synthesize a homodyne signal.
596
599
  zero_phase : float, optional
597
600
  Position of instrument response function in radians.
598
- Must be in range 0.0 to :math:`\pi`. The default is the 8th sample.
601
+ Must be in range [0, pi]. The default is the 8th sample.
599
602
  zero_stdev : float, optional
600
603
  Standard deviation of instrument response function in radians.
601
604
  Must be at least 1.5 samples and no more than one tenth of samples
@@ -683,7 +686,7 @@ def lifetime_to_signal(
683
686
  stdev = zero_stdev * scale # in sample units
684
687
 
685
688
  if zero_phase < 0 or zero_phase > 2.0 * math.pi:
686
- raise ValueError(f'{zero_phase=} out of range [0 .. 2 pi]')
689
+ raise ValueError(f'{zero_phase=} out of range [0, 2 pi]')
687
690
  if stdev < 1.5:
688
691
  raise ValueError(
689
692
  f'{zero_stdev=} < {1.5 / scale} cannot be sampled sufficiently'
@@ -749,9 +752,9 @@ def phasor_semicircle(
749
752
  Returns
750
753
  -------
751
754
  real : ndarray
752
- Real component of semicircle phasor coordinates.
755
+ Real component of phasor coordinates on universal semicircle.
753
756
  imag : ndarray
754
- Imaginary component of semicircle phasor coordinates.
757
+ Imaginary component of phasor coordinates on universal semicircle.
755
758
 
756
759
  Raises
757
760
  ------
@@ -1050,9 +1053,7 @@ def phasor_normalize(
1050
1053
  else:
1051
1054
  real = numpy.array(real_unnormalized, dtype, copy=True)
1052
1055
  imag = numpy.array(imag_unnormalized, real.dtype, copy=True)
1053
- mean = numpy.array(
1054
- mean_unnormalized, real.dtype, copy=None if samples == 1 else True
1055
- )
1056
+ mean = numpy.array(mean_unnormalized, real.dtype, copy=True)
1056
1057
 
1057
1058
  with numpy.errstate(divide='ignore', invalid='ignore'):
1058
1059
  numpy.divide(real, mean, out=real)
@@ -1158,7 +1159,8 @@ def phasor_calibrate(
1158
1159
  ValueError
1159
1160
  The array shapes of `real` and `imag`, or `reference_real` and
1160
1161
  `reference_imag` do not match.
1161
- Number of harmonics does not match the first axis of `real` and `imag`.
1162
+ Number of harmonics or frequencies does not match the first axis
1163
+ of `real` and `imag`.
1162
1164
 
1163
1165
  See Also
1164
1166
  --------
@@ -1257,13 +1259,24 @@ def phasor_calibrate(
1257
1259
  ),
1258
1260
  )
1259
1261
 
1262
+ frequency = numpy.asarray(frequency)
1263
+ frequency = frequency * harmonic
1264
+
1260
1265
  if has_harmonic_axis:
1261
1266
  if real.ndim == 0:
1262
- raise ValueError(f'{real.shape=} != {len(harmonic)=}')
1263
- if real.shape[0] != len(harmonic):
1264
- raise ValueError(f'{real.shape[0]=} != {len(harmonic)=}')
1265
- if reference_real.shape[0] != len(harmonic):
1266
- raise ValueError(f'{reference_real.shape[0]=} != {len(harmonic)=}')
1267
+ raise ValueError(
1268
+ f'{real.shape=} != {len(frequency)} frequencies or harmonics'
1269
+ )
1270
+ if real.shape[0] != len(frequency):
1271
+ raise ValueError(
1272
+ f'{real.shape[0]=} != {len(frequency)} '
1273
+ 'frequencies or harmonics'
1274
+ )
1275
+ if reference_real.shape[0] != len(frequency):
1276
+ raise ValueError(
1277
+ f'{reference_real.shape[0]=} != {len(frequency)} '
1278
+ 'frequencies or harmonics'
1279
+ )
1267
1280
  if reference_mean.shape != reference_real.shape[1:]:
1268
1281
  raise ValueError(
1269
1282
  f'{reference_mean.shape=} != {reference_real.shape[1:]=}'
@@ -1275,9 +1288,6 @@ def phasor_calibrate(
1275
1288
  f'{reference_mean.shape=} does not have harmonic axis'
1276
1289
  )
1277
1290
 
1278
- frequency = numpy.asarray(frequency)
1279
- frequency = frequency * harmonic
1280
-
1281
1291
  _, measured_re, measured_im = phasor_center(
1282
1292
  reference_mean,
1283
1293
  reference_real,
@@ -1295,6 +1305,24 @@ def phasor_calibrate(
1295
1305
  unit_conversion=unit_conversion,
1296
1306
  )
1297
1307
 
1308
+ skip_axis, axis = parse_skip_axis(
1309
+ skip_axis, real.ndim - int(has_harmonic_axis), has_harmonic_axis
1310
+ )
1311
+
1312
+ if has_harmonic_axis and any(skip_axis):
1313
+ known_re = numpy.expand_dims(
1314
+ known_re, tuple(range(1, measured_re.ndim))
1315
+ )
1316
+ known_re = numpy.broadcast_to(
1317
+ known_re, (len(frequency), *measured_re.shape[1:])
1318
+ )
1319
+ known_im = numpy.expand_dims(
1320
+ known_im, tuple(range(1, measured_im.ndim))
1321
+ )
1322
+ known_im = numpy.broadcast_to(
1323
+ known_im, (len(frequency), *measured_im.shape[1:])
1324
+ )
1325
+
1298
1326
  phi_zero, mod_zero = polar_from_reference_phasor(
1299
1327
  measured_re, measured_im, known_re, known_im
1300
1328
  )
@@ -1303,9 +1331,6 @@ def phasor_calibrate(
1303
1331
  if reverse:
1304
1332
  numpy.negative(phi_zero, out=phi_zero)
1305
1333
  numpy.reciprocal(mod_zero, out=mod_zero)
1306
- _, axis = _parse_skip_axis(
1307
- skip_axis, real.ndim - int(has_harmonic_axis), has_harmonic_axis
1308
- )
1309
1334
  if axis is not None:
1310
1335
  phi_zero = numpy.expand_dims(phi_zero, axis=axis)
1311
1336
  mod_zero = numpy.expand_dims(mod_zero, axis=axis)
@@ -2433,7 +2458,7 @@ def phasor_from_fret_donor(
2433
2458
  donor_lifetime: ArrayLike,
2434
2459
  *,
2435
2460
  fret_efficiency: ArrayLike = 0.0,
2436
- donor_freting: ArrayLike = 1.0,
2461
+ donor_fretting: ArrayLike = 1.0,
2437
2462
  donor_background: ArrayLike = 0.0,
2438
2463
  background_real: ArrayLike = 0.0,
2439
2464
  background_imag: ArrayLike = 0.0,
@@ -2459,9 +2484,9 @@ def phasor_from_fret_donor(
2459
2484
  donor_lifetime : array_like
2460
2485
  Lifetime of donor without FRET in ns.
2461
2486
  fret_efficiency : array_like, optional, default 0
2462
- FRET efficiency in range [0..1].
2463
- donor_freting : array_like, optional, default 1
2464
- Fraction of donors participating in FRET. Range [0..1].
2487
+ FRET efficiency in range [0, 1].
2488
+ donor_fretting : array_like, optional, default 1
2489
+ Fraction of donors participating in FRET. Range [0, 1].
2465
2490
  donor_background : array_like, optional, default 0
2466
2491
  Weight of background fluorescence in donor channel
2467
2492
  relative to fluorescence of donor without FRET.
@@ -2502,7 +2527,7 @@ def phasor_from_fret_donor(
2502
2527
  ... frequency=80,
2503
2528
  ... donor_lifetime=4.2,
2504
2529
  ... fret_efficiency=[0.0, 0.3, 1.0],
2505
- ... donor_freting=0.9,
2530
+ ... donor_fretting=0.9,
2506
2531
  ... donor_background=0.1,
2507
2532
  ... background_real=0.11,
2508
2533
  ... background_imag=0.12,
@@ -2516,7 +2541,7 @@ def phasor_from_fret_donor(
2516
2541
  omega,
2517
2542
  donor_lifetime,
2518
2543
  fret_efficiency,
2519
- donor_freting,
2544
+ donor_fretting,
2520
2545
  donor_background,
2521
2546
  background_real,
2522
2547
  background_imag,
@@ -2530,7 +2555,7 @@ def phasor_from_fret_acceptor(
2530
2555
  acceptor_lifetime: ArrayLike,
2531
2556
  *,
2532
2557
  fret_efficiency: ArrayLike = 0.0,
2533
- donor_freting: ArrayLike = 1.0,
2558
+ donor_fretting: ArrayLike = 1.0,
2534
2559
  donor_bleedthrough: ArrayLike = 0.0,
2535
2560
  acceptor_bleedthrough: ArrayLike = 0.0,
2536
2561
  acceptor_background: ArrayLike = 0.0,
@@ -2563,9 +2588,9 @@ def phasor_from_fret_acceptor(
2563
2588
  acceptor_lifetime : array_like
2564
2589
  Lifetime of acceptor in ns.
2565
2590
  fret_efficiency : array_like, optional, default 0
2566
- FRET efficiency in range [0..1].
2567
- donor_freting : array_like, optional, default 1
2568
- Fraction of donors participating in FRET. Range [0..1].
2591
+ FRET efficiency in range [0, 1].
2592
+ donor_fretting : array_like, optional, default 1
2593
+ Fraction of donors participating in FRET. Range [0, 1].
2569
2594
  donor_bleedthrough : array_like, optional, default 0
2570
2595
  Weight of donor fluorescence in acceptor channel
2571
2596
  relative to fluorescence of fully sensitized acceptor.
@@ -2618,7 +2643,7 @@ def phasor_from_fret_acceptor(
2618
2643
  ... donor_lifetime=4.2,
2619
2644
  ... acceptor_lifetime=3.0,
2620
2645
  ... fret_efficiency=[0.0, 0.3, 1.0],
2621
- ... donor_freting=0.9,
2646
+ ... donor_fretting=0.9,
2622
2647
  ... donor_bleedthrough=0.1,
2623
2648
  ... acceptor_bleedthrough=0.1,
2624
2649
  ... acceptor_background=0.1,
@@ -2635,7 +2660,7 @@ def phasor_from_fret_acceptor(
2635
2660
  donor_lifetime,
2636
2661
  acceptor_lifetime,
2637
2662
  fret_efficiency,
2638
- donor_freting,
2663
+ donor_fretting,
2639
2664
  donor_bleedthrough,
2640
2665
  acceptor_bleedthrough,
2641
2666
  acceptor_background,
@@ -2720,7 +2745,7 @@ def phasor_to_principal_plane(
2720
2745
  References
2721
2746
  ----------
2722
2747
 
2723
- .. [1] Franssen WMJ, Vergeldt FJ, Bader AN, van Amerongen H, & Terenzi C.
2748
+ .. [1] Franssen WMJ, Vergeldt FJ, Bader AN, van Amerongen H, and Terenzi C.
2724
2749
  `Full-harmonics phasor analysis: unravelling multiexponential trends
2725
2750
  in magnetic resonance imaging data
2726
2751
  <https://doi.org/10.1021/acs.jpclett.0c02319>`_.
@@ -2932,7 +2957,7 @@ def phasor_filter_median(
2932
2957
  raise ValueError(f'{real.shape=} != {imag.shape=}')
2933
2958
 
2934
2959
  prepend_axis = mean.ndim + 1 == real.ndim
2935
- _, axes = _parse_skip_axis(skip_axis, mean.ndim, prepend_axis)
2960
+ _, axes = parse_skip_axis(skip_axis, mean.ndim, prepend_axis)
2936
2961
 
2937
2962
  # in case mean is also filtered
2938
2963
  # if prepend_axis:
@@ -3006,6 +3031,209 @@ def phasor_filter_median(
3006
3031
  return mean, real, imag
3007
3032
 
3008
3033
 
3034
+ def phasor_filter_pawflim(
3035
+ mean: ArrayLike,
3036
+ real: ArrayLike,
3037
+ imag: ArrayLike,
3038
+ /,
3039
+ *,
3040
+ sigma: float = 2.0,
3041
+ levels: int = 1,
3042
+ harmonic: Sequence[int] | None = None,
3043
+ skip_axis: int | Sequence[int] | None = None,
3044
+ ) -> tuple[NDArray[Any], NDArray[Any], NDArray[Any]]:
3045
+ """Return pawFLIM wavelet-filtered phasor coordinates.
3046
+
3047
+ This function must only be used with calibrated, unprocessed phasor
3048
+ coordinates obtained from FLIM data. The coordinates must not be filtered,
3049
+ thresholded, or otherwise pre-processed.
3050
+
3051
+ The pawFLIM wavelet filter is described in [2]_.
3052
+
3053
+ Parameters
3054
+ ----------
3055
+ mean : array_like
3056
+ Intensity of phasor coordinates.
3057
+ real : array_like
3058
+ Real component of phasor coordinates to be filtered.
3059
+ Must have at least two harmonics in the first axis.
3060
+ imag : array_like
3061
+ Imaginary component of phasor coordinates to be filtered.
3062
+ Must have at least two harmonics in the first axis.
3063
+ sigma : float, optional
3064
+ Significance level to test difference between two phasors.
3065
+ Given in terms of the equivalent 1D standard deviations.
3066
+ sigma=2 corresponds to ~95% (or 5%) significance.
3067
+ levels : int, optional
3068
+ Number of levels for wavelet decomposition.
3069
+ Controls the maximum averaging area, which has a length of
3070
+ :math:`2^level`.
3071
+ harmonic : sequence of int or None, optional
3072
+ Harmonics included in first axis of `real` and `imag`.
3073
+ If None (default), the first axis of `real` and `imag` contains lower
3074
+ harmonics starting at and increasing by one.
3075
+ All harmonics must have a corresponding half or double harmonic.
3076
+ skip_axis : int or sequence of int, optional
3077
+ Axes in `mean` to exclude from filter.
3078
+ By default, all axes except harmonics are included.
3079
+
3080
+ Returns
3081
+ -------
3082
+ mean : ndarray
3083
+ Unchanged intensity of phasor coordinates.
3084
+ real : ndarray
3085
+ Filtered real component of phasor coordinates.
3086
+ imag : ndarray
3087
+ Filtered imaginary component of phasor coordinates.
3088
+
3089
+ Raises
3090
+ ------
3091
+ ValueError
3092
+ If `level` is less than 0.
3093
+ The array shapes of `mean`, `real`, and `imag` do not match.
3094
+ If `real` and `imag` have no harmonic axis.
3095
+ Number of harmonics in `harmonic` is less than 2 or does not match
3096
+ the first axis of `real` and `imag`.
3097
+ Not all harmonics in `harmonic` have a corresponding half
3098
+ or double harmonic.
3099
+
3100
+ References
3101
+ ----------
3102
+
3103
+ .. [2] Silberberg M, and Grecco H. `pawFLIM: reducing bias and
3104
+ uncertainty to enable lower photon count in FLIM experiments
3105
+ <https://doi.org/10.1088/2050-6120/aa72ab>`_.
3106
+ *Methods Appl Fluoresc*, 5(2): 024016 (2017)
3107
+
3108
+ Examples
3109
+ --------
3110
+ Apply a pawFLIM wavelet filter with four significance levels (sigma)
3111
+ and three decomposition levels:
3112
+
3113
+ >>> mean, real, imag = phasor_filter_pawflim(
3114
+ ... [[1, 1], [1, 1]],
3115
+ ... [[[0.5, 0.8], [0.5, 0.8]], [[0.2, 0.4], [0.2, 0.4]]],
3116
+ ... [[[0.5, 0.4], [0.5, 0.4]], [[0.4, 0.5], [0.4, 0.5]]],
3117
+ ... sigma=4,
3118
+ ... levels=3,
3119
+ ... harmonic=[1, 2],
3120
+ ... )
3121
+ >>> mean
3122
+ array([[1, 1],
3123
+ [1, 1]])
3124
+ >>> real
3125
+ array([[[0.65, 0.65],
3126
+ [0.65, 0.65]],
3127
+ [[0.3, 0.3],
3128
+ [0.3, 0.3]]])
3129
+ >>> imag
3130
+ array([[[0.45, 0.45],
3131
+ [0.45, 0.45]],
3132
+ [[0.45, 0.45],
3133
+ [0.45, 0.45]]])
3134
+
3135
+ """
3136
+ from pawflim import pawflim # type: ignore[import-untyped]
3137
+
3138
+ mean = numpy.asarray(mean)
3139
+ real = numpy.asarray(real)
3140
+ imag = numpy.asarray(imag)
3141
+
3142
+ if levels < 0:
3143
+ raise ValueError(f'{levels=} < 0')
3144
+ if levels == 0:
3145
+ return mean, real, imag
3146
+
3147
+ if mean.shape != real.shape[-mean.ndim if mean.ndim else 1 :]:
3148
+ raise ValueError(f'{mean.shape=} != {real.shape=}')
3149
+ if real.shape != imag.shape:
3150
+ raise ValueError(f'{real.shape=} != {imag.shape=}')
3151
+
3152
+ has_harmonic_axis = mean.ndim + 1 == real.ndim
3153
+ if not has_harmonic_axis:
3154
+ raise ValueError('no harmonic axis')
3155
+ if harmonic is None:
3156
+ harmonics, _ = parse_harmonic('all', real.shape[0])
3157
+ else:
3158
+ harmonics, _ = parse_harmonic(harmonic, None)
3159
+ if len(harmonics) < 2:
3160
+ raise ValueError(
3161
+ 'at least two harmonics required, ' f'got {len(harmonics)}'
3162
+ )
3163
+ if len(harmonics) != real.shape[0]:
3164
+ raise ValueError(
3165
+ 'number of harmonics does not match first axis of real and imag'
3166
+ )
3167
+
3168
+ mean = numpy.asarray(numpy.nan_to_num(mean), dtype=float)
3169
+ real = numpy.asarray(numpy.nan_to_num(real * mean), dtype=float)
3170
+ imag = numpy.asarray(numpy.nan_to_num(imag * mean), dtype=float)
3171
+
3172
+ mean_expanded = numpy.broadcast_to(mean, real.shape).copy()
3173
+ original_mean_expanded = mean_expanded.copy()
3174
+ real_filtered = real.copy()
3175
+ imag_filtered = imag.copy()
3176
+
3177
+ _, axes = parse_skip_axis(skip_axis, mean.ndim, True)
3178
+
3179
+ for index in numpy.ndindex(
3180
+ *(
3181
+ real.shape[ax]
3182
+ for ax in range(real.ndim)
3183
+ if ax not in axes and ax != 0
3184
+ )
3185
+ ):
3186
+ index_list: list[int | slice] = list(index)
3187
+ for ax in axes:
3188
+ index_list = index_list[:ax] + [slice(None)] + index_list[ax:]
3189
+ full_index = tuple(index_list)
3190
+
3191
+ processed_harmonics = set()
3192
+
3193
+ for h in harmonics:
3194
+ if h in processed_harmonics and (
3195
+ h * 4 in harmonics or h * 2 not in harmonics
3196
+ ):
3197
+ continue
3198
+ if h * 2 not in harmonics:
3199
+ raise ValueError(
3200
+ f'harmonic {h} does not have a corresponding half '
3201
+ f'or double harmonic in {harmonics}'
3202
+ )
3203
+ n = harmonics.index(h)
3204
+ n2 = harmonics.index(h * 2)
3205
+
3206
+ complex_phasor = numpy.empty(
3207
+ (3, *original_mean_expanded[n][full_index].shape),
3208
+ dtype=complex,
3209
+ )
3210
+ complex_phasor[0] = original_mean_expanded[n][full_index]
3211
+ complex_phasor[1] = real[n][full_index] + 1j * imag[n][full_index]
3212
+ complex_phasor[2] = (
3213
+ real[n2][full_index] + 1j * imag[n2][full_index]
3214
+ )
3215
+
3216
+ complex_phasor = pawflim(
3217
+ complex_phasor, n_sigmas=sigma, levels=levels
3218
+ )
3219
+
3220
+ for i, idx in enumerate([n, n2]):
3221
+ if harmonics[idx] in processed_harmonics:
3222
+ continue
3223
+ mean_expanded[idx][full_index] = complex_phasor[0].real
3224
+ real_filtered[idx][full_index] = complex_phasor[i + 1].real
3225
+ imag_filtered[idx][full_index] = complex_phasor[i + 1].imag
3226
+
3227
+ processed_harmonics.add(h)
3228
+ processed_harmonics.add(h * 2)
3229
+
3230
+ with numpy.errstate(divide='ignore', invalid='ignore'):
3231
+ real = numpy.asarray(numpy.divide(real_filtered, mean_expanded))
3232
+ imag = numpy.asarray(numpy.divide(imag_filtered, mean_expanded))
3233
+
3234
+ return mean, real, imag
3235
+
3236
+
3009
3237
  def phasor_threshold(
3010
3238
  mean: ArrayLike,
3011
3239
  real: ArrayLike,
@@ -3314,7 +3542,7 @@ def phasor_center(
3314
3542
  raise ValueError(f'{mean.shape=} != {real.shape=}')
3315
3543
 
3316
3544
  prepend_axis = mean.ndim + 1 == real.ndim
3317
- _, axis = _parse_skip_axis(skip_axis, mean.ndim, prepend_axis)
3545
+ _, axis = parse_skip_axis(skip_axis, mean.ndim, prepend_axis)
3318
3546
  if prepend_axis:
3319
3547
  mean = numpy.expand_dims(mean, axis=0)
3320
3548
 
@@ -3358,62 +3586,3 @@ def _median(
3358
3586
  numpy.nanmedian(real, **kwargs),
3359
3587
  numpy.nanmedian(imag, **kwargs),
3360
3588
  )
3361
-
3362
-
3363
- def _parse_skip_axis(
3364
- skip_axis: int | Sequence[int] | None,
3365
- /,
3366
- ndim: int,
3367
- prepend_axis: bool = False,
3368
- ) -> tuple[tuple[int, ...], tuple[int, ...]]:
3369
- """Return axes to skip and not to skip.
3370
-
3371
- This helper function is used to validate and parse `skip_axis`
3372
- parameters.
3373
-
3374
- Parameters
3375
- ----------
3376
- skip_axis : int or sequence of int, optional
3377
- Axes to skip. If None, no axes are skipped.
3378
- ndim : int
3379
- Dimensionality of array in which to skip axes.
3380
- prepend_axis : bool, optional
3381
- Prepend one dimension and include in `skip_axis`.
3382
-
3383
- Returns
3384
- -------
3385
- skip_axis
3386
- Ordered, positive values of `skip_axis`.
3387
- other_axis
3388
- Axes indices not included in `skip_axis`.
3389
-
3390
- Raises
3391
- ------
3392
- IndexError
3393
- If any `skip_axis` value is out of bounds of `ndim`.
3394
-
3395
- Examples
3396
- --------
3397
- >>> _parse_skip_axis((1, -2), 5)
3398
- ((1, 3), (0, 2, 4))
3399
-
3400
- >>> _parse_skip_axis((1, -2), 5, True)
3401
- ((0, 2, 4), (1, 3, 5))
3402
-
3403
- """
3404
- if ndim < 0:
3405
- raise ValueError(f'invalid {ndim=}')
3406
- if skip_axis is None:
3407
- if prepend_axis:
3408
- return (0,), tuple(range(1, ndim + 1))
3409
- return (), tuple(range(ndim))
3410
- if not isinstance(skip_axis, Sequence):
3411
- skip_axis = (skip_axis,)
3412
- if any(i >= ndim or i < -ndim for i in skip_axis):
3413
- raise IndexError(f'skip_axis={skip_axis} out of range for {ndim=}')
3414
- skip_axis = sorted(int(i % ndim) for i in skip_axis)
3415
- if prepend_axis:
3416
- skip_axis = [0] + [i + 1 for i in skip_axis]
3417
- ndim += 1
3418
- other_axis = tuple(i for i in range(ndim) if i not in skip_axis)
3419
- return tuple(skip_axis), other_axis