impy-array 2.4.2__tar.gz → 2.4.3__tar.gz
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.
- {impy_array-2.4.2 → impy_array-2.4.3}/PKG-INFO +8 -3
- {impy_array-2.4.2 → impy_array-2.4.3}/README.md +7 -2
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/__init__.py +1 -1
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/_imshow.py +30 -1
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/bases/metaarray.py +3 -3
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/imgarray.py +52 -12
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/lazy.py +8 -7
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/core.py +1 -1
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/correlation.py +12 -6
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/io.py +4 -4
- impy_array-2.4.3/impy/utils/slicer.py +148 -0
- impy_array-2.4.2/impy/utils/slicer.py +0 -89
- {impy_array-2.4.2 → impy_array-2.4.3}/.gitignore +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/LICENSE +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/__main__.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/_const.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/_types.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/array_api.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/__init__.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/_utils/__init__.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/_utils/_corr.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/_utils/_deconv.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/_utils/_docs.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/_utils/_filters.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/_utils/_glcm.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/_utils/_linalg.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/_utils/_misc.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/_utils/_plot.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/_utils/_process_numba.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/_utils/_skimage.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/_utils/_structures.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/_utils/_transform.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/axesmixin.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/bases/__init__.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/bases/overload.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/big.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/label.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/labeledarray.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/phasearray.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/specials.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/arrays/tiled.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/axes/__init__.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/axes/_axes.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/axes/_axes_tuple.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/axes/_axis.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/axes/_slicer.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/binder.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/collections.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/frame/__init__.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/frame/frames.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/lazy/__init__.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/lazy/core.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/lazy/random.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/random.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/roi.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/utils/__init__.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/utils/axesop.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/utils/deco.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/utils/gauss.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/utils/misc.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/utils/utilcls.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/viewer/__init__.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/viewer/utils.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/impy/viewer/viewer.py +0 -0
- {impy_array-2.4.2 → impy_array-2.4.3}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: impy-array
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.3
|
|
4
4
|
Summary: Speed up coding/extending image analysis in Python.
|
|
5
5
|
Project-URL: Download, https://github.com/hanjinliu/impy
|
|
6
6
|
Author-email: Hanjin Liu <liuhanjin-sc@g.ecc.u-tokyo.ac.jp>
|
|
@@ -120,8 +120,9 @@ or where to save the image. As a result, **your code will be very concise**:
|
|
|
120
120
|
import impy as ip
|
|
121
121
|
import numpy as np
|
|
122
122
|
|
|
123
|
-
img = ip.imread("path/to/image")
|
|
123
|
+
img = ip.imread("path/to/image.tif") # Read images with metadata.
|
|
124
124
|
img["z=3;t=0"].imshow() # Plot image slice at z=3 and t=0.
|
|
125
|
+
img["y=N//4:N//4*3"].imshow() # `N` for the size of the axis.
|
|
125
126
|
img_fil = img.gaussian_filter(sigma=2) # Paralell batch denoising. No more for loop!
|
|
126
127
|
img_prj = np.max(img_fil, axis="z") # Z-projection (numpy is aware of image axes!).
|
|
127
128
|
img_prj.imsave(f"Max-{img.name}") # Save in the same place. Don't spend time on searching for the directory!
|
|
@@ -134,6 +135,7 @@ img_prj.imsave(f"Max-{img.name}") # Save in the same place. Don't spend tim
|
|
|
134
135
|
- Tiff file (".tif", ".tiff")
|
|
135
136
|
- MRC file (".mrc", ".rec", ".st", ".map", ".map.gz")
|
|
136
137
|
- Zarr file (".zarr")
|
|
138
|
+
- ND2 file (".nd2")
|
|
137
139
|
- Other image file (".png", ".jpg")
|
|
138
140
|
|
|
139
141
|
### Lazy loading
|
|
@@ -162,7 +164,10 @@ ip.Const["RESOURCE"] = "cupy" # <- globally use GPU
|
|
|
162
164
|
|
|
163
165
|
### Seamless interface between `napari`
|
|
164
166
|
|
|
165
|
-
[napari](https://github.com/napari/napari) is an interactive viewer for multi-dimensional
|
|
167
|
+
[napari](https://github.com/napari/napari) is an interactive viewer for multi-dimensional
|
|
168
|
+
images. `impy` has a **simple and efficient interface** with it, via the object `ip.gui`.
|
|
169
|
+
Since `ImgArray` is tagged with image metadata, you don't have to care about axes or
|
|
170
|
+
scales. Just run
|
|
166
171
|
|
|
167
172
|
```python
|
|
168
173
|
ip.gui.add(img)
|
|
@@ -48,8 +48,9 @@ or where to save the image. As a result, **your code will be very concise**:
|
|
|
48
48
|
import impy as ip
|
|
49
49
|
import numpy as np
|
|
50
50
|
|
|
51
|
-
img = ip.imread("path/to/image")
|
|
51
|
+
img = ip.imread("path/to/image.tif") # Read images with metadata.
|
|
52
52
|
img["z=3;t=0"].imshow() # Plot image slice at z=3 and t=0.
|
|
53
|
+
img["y=N//4:N//4*3"].imshow() # `N` for the size of the axis.
|
|
53
54
|
img_fil = img.gaussian_filter(sigma=2) # Paralell batch denoising. No more for loop!
|
|
54
55
|
img_prj = np.max(img_fil, axis="z") # Z-projection (numpy is aware of image axes!).
|
|
55
56
|
img_prj.imsave(f"Max-{img.name}") # Save in the same place. Don't spend time on searching for the directory!
|
|
@@ -62,6 +63,7 @@ img_prj.imsave(f"Max-{img.name}") # Save in the same place. Don't spend tim
|
|
|
62
63
|
- Tiff file (".tif", ".tiff")
|
|
63
64
|
- MRC file (".mrc", ".rec", ".st", ".map", ".map.gz")
|
|
64
65
|
- Zarr file (".zarr")
|
|
66
|
+
- ND2 file (".nd2")
|
|
65
67
|
- Other image file (".png", ".jpg")
|
|
66
68
|
|
|
67
69
|
### Lazy loading
|
|
@@ -90,7 +92,10 @@ ip.Const["RESOURCE"] = "cupy" # <- globally use GPU
|
|
|
90
92
|
|
|
91
93
|
### Seamless interface between `napari`
|
|
92
94
|
|
|
93
|
-
[napari](https://github.com/napari/napari) is an interactive viewer for multi-dimensional
|
|
95
|
+
[napari](https://github.com/napari/napari) is an interactive viewer for multi-dimensional
|
|
96
|
+
images. `impy` has a **simple and efficient interface** with it, via the object `ip.gui`.
|
|
97
|
+
Since `ImgArray` is tagged with image metadata, you don't have to care about axes or
|
|
98
|
+
scales. Just run
|
|
94
99
|
|
|
95
100
|
```python
|
|
96
101
|
ip.gui.add(img)
|
|
@@ -177,4 +177,33 @@ def _imshow_matplotlib(self: LabeledArray, label, dims, **kwargs) -> LabeledArra
|
|
|
177
177
|
def _imshow_ipywidgets(arr: LabeledArray, label: bool, dims, **kwargs) -> LabeledArray:
|
|
178
178
|
from ipywidgets import interact
|
|
179
179
|
|
|
180
|
-
raise NotImplementedError()
|
|
180
|
+
raise NotImplementedError()
|
|
181
|
+
|
|
182
|
+
@register("ndv")
|
|
183
|
+
def _imshow_ndv(self: LabeledArray, label: bool, dims, **kwargs) -> LabeledArray:
|
|
184
|
+
from ndv import NDViewer
|
|
185
|
+
|
|
186
|
+
app, should_exec = _get_app()
|
|
187
|
+
if "c" in self.axes:
|
|
188
|
+
channel_axis = self.axes.index("c")
|
|
189
|
+
else:
|
|
190
|
+
channel_axis = None
|
|
191
|
+
viewer = NDViewer(self, channel_axis=channel_axis)
|
|
192
|
+
viewer.show()
|
|
193
|
+
if should_exec:
|
|
194
|
+
app.exec()
|
|
195
|
+
return self
|
|
196
|
+
|
|
197
|
+
def _get_app():
|
|
198
|
+
# ported from ndv
|
|
199
|
+
import sys
|
|
200
|
+
from qtpy.QtWidgets import QApplication
|
|
201
|
+
|
|
202
|
+
is_ipython = False
|
|
203
|
+
if (app := QApplication.instance()) is None:
|
|
204
|
+
app = QApplication([])
|
|
205
|
+
app.setApplicationName("ndv")
|
|
206
|
+
elif (ipy := sys.modules.get("IPython")) and (shell := ipy.get_ipython()):
|
|
207
|
+
is_ipython = str(shell.active_eventloop).startswith("qt")
|
|
208
|
+
|
|
209
|
+
return app, not is_ipython
|
|
@@ -119,7 +119,7 @@ class MetaArray(AxesMixin, np.ndarray):
|
|
|
119
119
|
return self.axes.tuple(super().shape)
|
|
120
120
|
|
|
121
121
|
def __getitem__(self, key: SupportSlicing) -> Self:
|
|
122
|
-
key = slicer.solve_slicer(key, self.axes)
|
|
122
|
+
key = slicer.solve_slicer(key, self.axes, self.shape)
|
|
123
123
|
|
|
124
124
|
if isinstance(key, np.ndarray):
|
|
125
125
|
key = self._broadcast(key)
|
|
@@ -133,7 +133,7 @@ class MetaArray(AxesMixin, np.ndarray):
|
|
|
133
133
|
return out
|
|
134
134
|
|
|
135
135
|
def __setitem__(self, key: SupportSlicing, value):
|
|
136
|
-
key = slicer.solve_slicer(key, self.axes)
|
|
136
|
+
key = slicer.solve_slicer(key, self.axes, self.shape)
|
|
137
137
|
|
|
138
138
|
if isinstance(key, MetaArray) and key.dtype == bool:
|
|
139
139
|
key = axesop.add_axes(self.axes, self.shape, key, key.axes)
|
|
@@ -181,7 +181,7 @@ class MetaArray(AxesMixin, np.ndarray):
|
|
|
181
181
|
if indexer is not None:
|
|
182
182
|
kwargs.update(indexer)
|
|
183
183
|
|
|
184
|
-
key = slicer.solve_slicer(kwargs, self.axes)
|
|
184
|
+
key = slicer.solve_slicer(kwargs, self.axes, self.shape)
|
|
185
185
|
out = super().__getitem__(key) # get item as np.ndarray
|
|
186
186
|
|
|
187
187
|
if isinstance(out, self.__class__): # cannot set attribution to such as numpy.int32
|
|
@@ -6,13 +6,13 @@ from functools import partial
|
|
|
6
6
|
from scipy import ndimage as ndi
|
|
7
7
|
from typing import TYPE_CHECKING, Literal, Sequence, Iterable, Callable, overload, Any
|
|
8
8
|
|
|
9
|
-
from .labeledarray import LabeledArray
|
|
10
|
-
from .label import Label
|
|
11
|
-
from .phasearray import PhaseArray
|
|
12
|
-
from .specials import PropArray
|
|
13
|
-
from .tiled import TiledAccessor
|
|
9
|
+
from impy.arrays.labeledarray import LabeledArray
|
|
10
|
+
from impy.arrays.label import Label
|
|
11
|
+
from impy.arrays.phasearray import PhaseArray
|
|
12
|
+
from impy.arrays.specials import PropArray
|
|
13
|
+
from impy.arrays.tiled import TiledAccessor
|
|
14
14
|
|
|
15
|
-
from ._utils import _filters, _linalg, _deconv, _misc, _glcm, _docs, _transform, _structures, _corr
|
|
15
|
+
from impy.arrays._utils import _filters, _linalg, _deconv, _misc, _glcm, _docs, _transform, _structures, _corr
|
|
16
16
|
|
|
17
17
|
from impy.arrays.bases.metaarray import _NoValue
|
|
18
18
|
from impy.utils.axesop import add_axes, switch_slice, complement_axes, find_first_appeared
|
|
@@ -217,6 +217,44 @@ class ImgArray(LabeledArray):
|
|
|
217
217
|
c_axes=complement_axes(dims, self.axes),
|
|
218
218
|
kwargs=dict(matrix=mx, order=order, mode=mode, cval=cval),
|
|
219
219
|
)
|
|
220
|
+
|
|
221
|
+
@_docs.write_docs
|
|
222
|
+
@dims_to_spatial_axes
|
|
223
|
+
@same_dtype(asfloat=True)
|
|
224
|
+
@check_input_and_output
|
|
225
|
+
def shift(
|
|
226
|
+
self, translation, order: int = 3, mode: PaddingMode = "constant",
|
|
227
|
+
cval: float = 0, prefilter: bool | None = None, dims: Dims = None,
|
|
228
|
+
update: bool = False,
|
|
229
|
+
) -> ImgArray:
|
|
230
|
+
"""
|
|
231
|
+
2D stretching of an image from a point.
|
|
232
|
+
|
|
233
|
+
Parameters
|
|
234
|
+
----------
|
|
235
|
+
translation : array-like
|
|
236
|
+
Translation in pixels. Must match the length of spatial dimensions.
|
|
237
|
+
{mode}{cval}{dims}{order}
|
|
238
|
+
|
|
239
|
+
Returns
|
|
240
|
+
-------
|
|
241
|
+
ImgArray
|
|
242
|
+
Stretched image.
|
|
243
|
+
"""
|
|
244
|
+
if isinstance(cval, str) and hasattr(np, cval):
|
|
245
|
+
cval = getattr(np, cval)(self.value)
|
|
246
|
+
elif callable(cval):
|
|
247
|
+
cval = cval(self.value)
|
|
248
|
+
|
|
249
|
+
prefilter = prefilter or order > 1
|
|
250
|
+
mtx = _transform.compose_affine_matrix(translation=translation)
|
|
251
|
+
|
|
252
|
+
return self._apply_dask(
|
|
253
|
+
_transform.warp,
|
|
254
|
+
c_axes=complement_axes(dims, self.axes),
|
|
255
|
+
kwargs=dict(matrix=mtx, order=order, mode=mode, cval=cval,
|
|
256
|
+
prefilter=prefilter)
|
|
257
|
+
)
|
|
220
258
|
|
|
221
259
|
@_docs.write_docs
|
|
222
260
|
@dims_to_spatial_axes
|
|
@@ -2836,7 +2874,7 @@ class ImgArray(LabeledArray):
|
|
|
2836
2874
|
if key is None:
|
|
2837
2875
|
slices = (slice(None),) * ndim
|
|
2838
2876
|
else:
|
|
2839
|
-
slices = solve_slicer(key, Axes(dims))
|
|
2877
|
+
slices = solve_slicer(key, Axes(dims), self.shape)
|
|
2840
2878
|
dtype = np.complex128 if double_precision else np.complex64
|
|
2841
2879
|
|
|
2842
2880
|
# Calculate exp(-ikx)
|
|
@@ -3957,8 +3995,8 @@ class ImgArray(LabeledArray):
|
|
|
3957
3995
|
@check_input_and_output
|
|
3958
3996
|
def drift_correction(
|
|
3959
3997
|
self,
|
|
3960
|
-
shift: Coords = None,
|
|
3961
|
-
ref: ImgArray = None,
|
|
3998
|
+
shift: Coords | None = None,
|
|
3999
|
+
ref: ImgArray | Any | None = None,
|
|
3962
4000
|
*,
|
|
3963
4001
|
zero_ave: bool = True,
|
|
3964
4002
|
along: AxisLike | None = None,
|
|
@@ -3977,8 +4015,10 @@ class ImgArray(LabeledArray):
|
|
|
3977
4015
|
shift : DataFrame or (N, D) array, optional
|
|
3978
4016
|
Translation vectors. If DataFrame, it must have columns named with all the symbols
|
|
3979
4017
|
contained in ``dims``.
|
|
3980
|
-
ref : ImgArray, optional
|
|
3981
|
-
The reference n-D image to determine drift, if ``shift`` was not given.
|
|
4018
|
+
ref : ImgArray or slicer, optional
|
|
4019
|
+
The reference n-D image to determine drift, if ``shift`` was not given. This
|
|
4020
|
+
parameter can be a slicer, which will be used to slice the image to make a
|
|
4021
|
+
reference.
|
|
3982
4022
|
zero_ave : bool, default is True
|
|
3983
4023
|
If True, average shift will be zero.
|
|
3984
4024
|
along : AxisLike, optional
|
|
@@ -4008,7 +4048,7 @@ class ImgArray(LabeledArray):
|
|
|
4008
4048
|
if ref is None:
|
|
4009
4049
|
ref = self
|
|
4010
4050
|
elif not isinstance(ref, ImgArray):
|
|
4011
|
-
|
|
4051
|
+
ref = self[ref]
|
|
4012
4052
|
if ref.axes != [along] + dims:
|
|
4013
4053
|
from itertools import product
|
|
4014
4054
|
_c_axes = complement_axes([along] + dims, str(ref.axes))
|
|
@@ -129,7 +129,7 @@ class LazyImgArray(AxesMixin):
|
|
|
129
129
|
return xp.asnumpy(self.value.compute())
|
|
130
130
|
|
|
131
131
|
def __getitem__(self, key):
|
|
132
|
-
key = slicer.solve_slicer(key, self.axes)
|
|
132
|
+
key = slicer.solve_slicer(key, self.axes, self.shape)
|
|
133
133
|
|
|
134
134
|
new_axes = slice_axes(self.axes, key)
|
|
135
135
|
out = self.__class__(
|
|
@@ -1149,10 +1149,11 @@ class LazyImgArray(AxesMixin):
|
|
|
1149
1149
|
@same_dtype(asfloat=True)
|
|
1150
1150
|
@check_input_and_output_lazy
|
|
1151
1151
|
@dims_to_spatial_axes
|
|
1152
|
-
def drift_correction(
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1152
|
+
def drift_correction(
|
|
1153
|
+
self, shift: Coords | None = None, ref: ImgArray | Any | None = None, *,
|
|
1154
|
+
zero_ave: bool = True, along: str = None, dims: Dims = 2,
|
|
1155
|
+
update: bool = False, **affine_kwargs,
|
|
1156
|
+
) -> LazyImgArray:
|
|
1156
1157
|
if along is None:
|
|
1157
1158
|
along = find_first_appeared("tpzcia", include=self.axes, exclude=dims)
|
|
1158
1159
|
elif len(along) != 1:
|
|
@@ -1171,8 +1172,8 @@ class LazyImgArray(AxesMixin):
|
|
|
1171
1172
|
UserWarning)
|
|
1172
1173
|
dims = _dims
|
|
1173
1174
|
elif not isinstance(ref, self.__class__):
|
|
1174
|
-
|
|
1175
|
-
|
|
1175
|
+
ref = self[ref]
|
|
1176
|
+
if ref.axes != along + dims:
|
|
1176
1177
|
raise ValueError(f"Arguments `along`({along}) + `dims`({dims}) do not match "
|
|
1177
1178
|
f"axes of `ref`({ref.axes})")
|
|
1178
1179
|
|
|
@@ -570,7 +570,7 @@ def imread(
|
|
|
570
570
|
scale_unit = {a: scale_unit for a in "zyx"}
|
|
571
571
|
|
|
572
572
|
if is_memmap and axes is not None:
|
|
573
|
-
sl = solve_slicer(key, Axes(axes))
|
|
573
|
+
sl = solve_slicer(key, Axes(axes), img.shape)
|
|
574
574
|
axes = "".join(a for a, k in zip(axes, sl) if not isinstance(k, int))
|
|
575
575
|
img = np.asarray(img[sl], dtype=dtype)
|
|
576
576
|
|
|
@@ -817,8 +817,8 @@ def manders_coloc(
|
|
|
817
817
|
|
|
818
818
|
:math:`r = \frac{\sum_{i \in I_{ref}} I_i}{\sum_{i} I_i}`
|
|
819
819
|
|
|
820
|
-
This value is NOT independent of background intensity. You need to correctly
|
|
821
|
-
background from self. This value is NOT interchangable between channels.
|
|
820
|
+
This value is NOT independent of background intensity. You need to correctly
|
|
821
|
+
subtract background from self. This value is NOT interchangable between channels.
|
|
822
822
|
|
|
823
823
|
Parameters
|
|
824
824
|
----------
|
|
@@ -832,13 +832,19 @@ def manders_coloc(
|
|
|
832
832
|
Correlation coefficient(s).
|
|
833
833
|
"""
|
|
834
834
|
if img1.dtype != bool:
|
|
835
|
-
raise TypeError("`
|
|
835
|
+
raise TypeError("`img1` must be a binary image.")
|
|
836
836
|
if img0.shape != img1.shape:
|
|
837
837
|
raise ValueError(f"Shape mismatch. `img0` has shape {img0.shape} but `img1` "
|
|
838
838
|
f"has shape {img1.shape}")
|
|
839
|
-
if
|
|
840
|
-
|
|
841
|
-
|
|
839
|
+
if not isinstance(img1, ImgArray):
|
|
840
|
+
img1 = ip_asarray(img1, axes=img0.axes)
|
|
841
|
+
elif img0.axes != img1.axes:
|
|
842
|
+
warn(
|
|
843
|
+
f"Axes mismatch. `img0` has axes {img0.axes} but `img1` has axes "
|
|
844
|
+
f"{img1.axes}. Result may be wrong due to this mismatch.",
|
|
845
|
+
UserWarning,
|
|
846
|
+
stacklevel=2,
|
|
847
|
+
)
|
|
842
848
|
img0 = img0.as_float()
|
|
843
849
|
total = np.sum(img0, axis=dims)
|
|
844
850
|
img0 = img0.copy()
|
|
@@ -365,8 +365,9 @@ def _(path: str, memmap: bool = False) -> ImageData:
|
|
|
365
365
|
@IO.mark_writer(".tif", ".tiff")
|
|
366
366
|
def _(path: str, img: ImpyArray, lazy: bool = False):
|
|
367
367
|
"""The TIFF writer."""
|
|
368
|
+
from tifffile import imwrite, memmap
|
|
369
|
+
|
|
368
370
|
if lazy:
|
|
369
|
-
from tifffile import memmap
|
|
370
371
|
from dask import array as da
|
|
371
372
|
kwargs = _get_ijmeta_from_img(img, update_lut=False)
|
|
372
373
|
mmap = memmap(str(path), shape=img.shape, dtype=img.dtype, **kwargs)
|
|
@@ -375,7 +376,6 @@ def _(path: str, img: ImpyArray, lazy: bool = False):
|
|
|
375
376
|
da.store(img_dask, writer)
|
|
376
377
|
return
|
|
377
378
|
|
|
378
|
-
from tifffile import imwrite
|
|
379
379
|
rest_axes = complement_axes(img.axes, "tzcyx")
|
|
380
380
|
new_axes = ""
|
|
381
381
|
for a in img.axes:
|
|
@@ -394,11 +394,11 @@ def _(path: str, img: ImpyArray, lazy: bool = False):
|
|
|
394
394
|
img_new.set_scale(img)
|
|
395
395
|
img = img_new
|
|
396
396
|
|
|
397
|
-
warnings.warn("Image axes changed", UserWarning, stacklevel=2)
|
|
397
|
+
warnings.warn(f"Image axes changed", UserWarning, stacklevel=2)
|
|
398
398
|
|
|
399
399
|
img = img.sort_axes()
|
|
400
400
|
if img.dtype == "bool":
|
|
401
|
-
img = img.astype(np.uint8)
|
|
401
|
+
img = img.astype(np.uint8) # tif does not support bool
|
|
402
402
|
imsave_kwargs = _get_ijmeta_from_img(img, update_lut=True)
|
|
403
403
|
imwrite(path, img, **imsave_kwargs)
|
|
404
404
|
return None
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from functools import lru_cache
|
|
5
|
+
import math
|
|
6
|
+
from typing import Any, Mapping
|
|
7
|
+
from impy._types import Slices
|
|
8
|
+
from impy.axes import Slicer, Axes
|
|
9
|
+
|
|
10
|
+
__all__ = ["str_to_slice", "solve_slicer"]
|
|
11
|
+
|
|
12
|
+
class _Slicer(ABC):
|
|
13
|
+
def str_to_slice(self, v: str) -> list[int] | slice | int:
|
|
14
|
+
v = v.replace(" ", "")
|
|
15
|
+
if "," in v:
|
|
16
|
+
sl = sum((self._range_to_list(v) for v in v.split(",")), [])
|
|
17
|
+
elif ":" in v:
|
|
18
|
+
sl = slice(*map(self._int_or_none, v.split(":")))
|
|
19
|
+
else:
|
|
20
|
+
sl = self._int(v)
|
|
21
|
+
return sl
|
|
22
|
+
|
|
23
|
+
def _range_to_list(self, v: str) -> list[int]:
|
|
24
|
+
if ":" in v:
|
|
25
|
+
s, e = v.split(":")
|
|
26
|
+
return list(range(self._int(s), self._int(e)))
|
|
27
|
+
else:
|
|
28
|
+
return [self._int(v)]
|
|
29
|
+
|
|
30
|
+
def _int_or_none(self, v: str) -> int | None:
|
|
31
|
+
if v:
|
|
32
|
+
return self._int(v)
|
|
33
|
+
else:
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def _int(self, v: str) -> int:
|
|
38
|
+
"""Convert a string to an integer."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def str_to_slice(v: str) -> list[int] | slice | int:
|
|
42
|
+
return ConstSlicer().str_to_slice(v)
|
|
43
|
+
|
|
44
|
+
class ConstSlicer(_Slicer):
|
|
45
|
+
def _int(self, v: str) -> int:
|
|
46
|
+
return int(v)
|
|
47
|
+
|
|
48
|
+
class EvalSlicer(_Slicer):
|
|
49
|
+
def __init__(self, size: int):
|
|
50
|
+
self._size = size
|
|
51
|
+
|
|
52
|
+
def _int(self, v: str) -> int:
|
|
53
|
+
ns = {
|
|
54
|
+
"N": self._size,
|
|
55
|
+
"int": int,
|
|
56
|
+
"ceil": math.ceil,
|
|
57
|
+
"floor": math.floor,
|
|
58
|
+
"round": round,
|
|
59
|
+
"__builtins__": {},
|
|
60
|
+
}
|
|
61
|
+
out = eval(v, ns)
|
|
62
|
+
if not hasattr(out, "__index__"):
|
|
63
|
+
raise ValueError(f"Invalid index: {v!r}.")
|
|
64
|
+
return out.__index__()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def axis_targeted_slicing(
|
|
68
|
+
axes: tuple[str, ...],
|
|
69
|
+
string: str,
|
|
70
|
+
shape: tuple[int, ...] | None = None,
|
|
71
|
+
) -> Slices:
|
|
72
|
+
"""
|
|
73
|
+
Make a conventional slices from an axis-targeted slicing string.
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
axes : str
|
|
78
|
+
Axes of input ndarray.
|
|
79
|
+
string : str
|
|
80
|
+
Axis-targeted slicing string. If an axis that does not exist in `axes` is
|
|
81
|
+
contained, this function will raise ValueError.
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
-------
|
|
85
|
+
slices
|
|
86
|
+
"""
|
|
87
|
+
if "N" in string:
|
|
88
|
+
return axis_targeted_slicing_eval(axes, string, shape)
|
|
89
|
+
else:
|
|
90
|
+
return axis_targeted_slicing_const(axes, string)
|
|
91
|
+
|
|
92
|
+
@lru_cache
|
|
93
|
+
def axis_targeted_slicing_const(axes: tuple[str, ...], string: str) -> Slices:
|
|
94
|
+
keylist = string.replace(" ", "").split(";")
|
|
95
|
+
dict_slicer: dict[str, Any] = {}
|
|
96
|
+
|
|
97
|
+
for k in keylist:
|
|
98
|
+
if k.count("=") != 1:
|
|
99
|
+
raise ValueError(f"Informal axis-targeted slicing: {k!r}.")
|
|
100
|
+
axis, sl_str = k.split("=")
|
|
101
|
+
try:
|
|
102
|
+
sl = str_to_slice(sl_str)
|
|
103
|
+
except ValueError:
|
|
104
|
+
raise ValueError(f"Informal axis-targeted slicing: {k!r}.")
|
|
105
|
+
else:
|
|
106
|
+
dict_slicer[axis] = sl
|
|
107
|
+
|
|
108
|
+
return _dict_to_slice(dict_slicer, axes)
|
|
109
|
+
|
|
110
|
+
def axis_targeted_slicing_eval(
|
|
111
|
+
axes: tuple[str, ...],
|
|
112
|
+
string: str,
|
|
113
|
+
shape: tuple[int, ...] | None = None,
|
|
114
|
+
) -> Slices:
|
|
115
|
+
keylist = string.replace(" ", "").split(";")
|
|
116
|
+
dict_slicer: dict[str, Any] = {}
|
|
117
|
+
|
|
118
|
+
for k in keylist:
|
|
119
|
+
if k.count("=") != 1:
|
|
120
|
+
raise ValueError(f"Informal axis-targeted slicing: {k!r}.")
|
|
121
|
+
axis, sl_str = k.split("=")
|
|
122
|
+
try:
|
|
123
|
+
size = shape[axes.index(axis)]
|
|
124
|
+
sl = EvalSlicer(size).str_to_slice(sl_str)
|
|
125
|
+
except ValueError:
|
|
126
|
+
raise ValueError(f"Informal axis-targeted slicing: {k!r}.")
|
|
127
|
+
else:
|
|
128
|
+
dict_slicer[axis] = sl
|
|
129
|
+
|
|
130
|
+
return _dict_to_slice(dict_slicer, axes)
|
|
131
|
+
|
|
132
|
+
def _dict_to_slice(sl: dict[str, Any], axes: tuple[str, ...]):
|
|
133
|
+
sl_list = [slice(None)] * len(axes)
|
|
134
|
+
|
|
135
|
+
for k, v in sl.items():
|
|
136
|
+
idx = axes.index(k)
|
|
137
|
+
sl_list[idx] = v
|
|
138
|
+
|
|
139
|
+
return tuple(sl_list)
|
|
140
|
+
|
|
141
|
+
def solve_slicer(key: Any, axes: Axes, shape: tuple[int, ...] | None = None) -> Slices:
|
|
142
|
+
if isinstance(key, str):
|
|
143
|
+
key = axis_targeted_slicing(tuple(axes), key, shape)
|
|
144
|
+
|
|
145
|
+
elif isinstance(key, (Mapping, Slicer)):
|
|
146
|
+
key = axes.create_slice(key)
|
|
147
|
+
|
|
148
|
+
return key
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
import re
|
|
3
|
-
from functools import lru_cache
|
|
4
|
-
from typing import Any, Mapping
|
|
5
|
-
from .._types import Slices
|
|
6
|
-
from ..axes import Slicer, Axes
|
|
7
|
-
|
|
8
|
-
__all__ = ["str_to_slice", "axis_targeted_slicing", "solve_slicer"]
|
|
9
|
-
|
|
10
|
-
def _range_to_list(v: str) -> list[int]:
|
|
11
|
-
"""
|
|
12
|
-
"1,3,5" -> [1,3,5]
|
|
13
|
-
"2,4:6,9" -> [2,4,5,6,9]
|
|
14
|
-
"""
|
|
15
|
-
if ":" in v:
|
|
16
|
-
s, e = v.split(":")
|
|
17
|
-
return list(range(int(s), int(e)))
|
|
18
|
-
else:
|
|
19
|
-
return [int(v)]
|
|
20
|
-
|
|
21
|
-
def _int_or_none(v: str) -> int | None:
|
|
22
|
-
if v:
|
|
23
|
-
return int(v)
|
|
24
|
-
else:
|
|
25
|
-
return None
|
|
26
|
-
|
|
27
|
-
def str_to_slice(v: str) -> list[int] | slice | int:
|
|
28
|
-
v = re.sub(" ", "", v)
|
|
29
|
-
if "," in v:
|
|
30
|
-
sl = sum((_range_to_list(v) for v in v.split(",")), [])
|
|
31
|
-
elif ":" in v:
|
|
32
|
-
sl = slice(*map(_int_or_none, v.split(":")))
|
|
33
|
-
else:
|
|
34
|
-
sl = int(v)
|
|
35
|
-
return sl
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
@lru_cache
|
|
39
|
-
def axis_targeted_slicing(axes: tuple[str, ...], string: str) -> Slices:
|
|
40
|
-
"""
|
|
41
|
-
Make a conventional slices from an axis-targeted slicing string.
|
|
42
|
-
|
|
43
|
-
Parameters
|
|
44
|
-
----------
|
|
45
|
-
ndim : int
|
|
46
|
-
Number of dimension of the array which will be sliced.
|
|
47
|
-
axes : str
|
|
48
|
-
Axes of input ndarray.
|
|
49
|
-
string : str
|
|
50
|
-
Axis-targeted slicing string. If an axis that does not exist in `axes` is
|
|
51
|
-
contained, this function will raise ValueError.
|
|
52
|
-
|
|
53
|
-
Returns
|
|
54
|
-
-------
|
|
55
|
-
slices
|
|
56
|
-
"""
|
|
57
|
-
keylist = re.sub(" ", "", string).split(";")
|
|
58
|
-
dict_slicer: dict[str, Any] = {}
|
|
59
|
-
|
|
60
|
-
for k in keylist:
|
|
61
|
-
if k.count("=") != 1:
|
|
62
|
-
raise ValueError(f"Informal axis-targeted slicing: {k!r}.")
|
|
63
|
-
axis, sl_str = k.split("=")
|
|
64
|
-
try:
|
|
65
|
-
sl = str_to_slice(sl_str)
|
|
66
|
-
except ValueError:
|
|
67
|
-
raise ValueError(f"Informal axis-targeted slicing: {k!r}.")
|
|
68
|
-
else:
|
|
69
|
-
dict_slicer[axis] = sl
|
|
70
|
-
|
|
71
|
-
return dict_to_slice(dict_slicer, axes)
|
|
72
|
-
|
|
73
|
-
def dict_to_slice(sl: dict[str, Any], axes: tuple[str, ...]):
|
|
74
|
-
sl_list = [slice(None)] * len(axes)
|
|
75
|
-
|
|
76
|
-
for k, v in sl.items():
|
|
77
|
-
idx = axes.index(k)
|
|
78
|
-
sl_list[idx] = v
|
|
79
|
-
|
|
80
|
-
return tuple(sl_list)
|
|
81
|
-
|
|
82
|
-
def solve_slicer(key: Any, axes: Axes) -> Slices:
|
|
83
|
-
if isinstance(key, str):
|
|
84
|
-
key = axis_targeted_slicing(tuple(axes), key)
|
|
85
|
-
|
|
86
|
-
elif isinstance(key, (Mapping, Slicer)):
|
|
87
|
-
key = axes.create_slice(key)
|
|
88
|
-
|
|
89
|
-
return key
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|