impy-array 2.4.1__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.1 → impy_array-2.4.3}/PKG-INFO +9 -4
- {impy_array-2.4.1 → impy_array-2.4.3}/README.md +7 -2
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/__init__.py +1 -1
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/_imshow.py +30 -1
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/_utils/_glcm.py +1 -1
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/_utils/_structures.py +1 -1
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/bases/metaarray.py +3 -3
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/imgarray.py +52 -12
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/lazy.py +16 -9
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/tiled.py +5 -1
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/core.py +6 -3
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/correlation.py +12 -6
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/io.py +4 -4
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/utils/gauss.py +1 -1
- impy_array-2.4.3/impy/utils/slicer.py +148 -0
- impy_array-2.4.1/impy/utils/slicer.py +0 -89
- {impy_array-2.4.1 → impy_array-2.4.3}/.gitignore +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/LICENSE +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/__main__.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/_const.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/_types.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/array_api.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/__init__.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/_utils/__init__.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/_utils/_corr.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/_utils/_deconv.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/_utils/_docs.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/_utils/_filters.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/_utils/_linalg.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/_utils/_misc.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/_utils/_plot.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/_utils/_process_numba.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/_utils/_skimage.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/_utils/_transform.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/axesmixin.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/bases/__init__.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/bases/overload.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/big.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/label.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/labeledarray.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/phasearray.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/arrays/specials.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/axes/__init__.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/axes/_axes.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/axes/_axes_tuple.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/axes/_axis.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/axes/_slicer.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/binder.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/collections.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/frame/__init__.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/frame/frames.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/lazy/__init__.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/lazy/core.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/lazy/random.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/random.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/roi.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/utils/__init__.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/utils/axesop.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/utils/deco.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/utils/misc.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/utils/utilcls.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/viewer/__init__.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/viewer/utils.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/impy/viewer/viewer.py +0 -0
- {impy_array-2.4.1 → impy_array-2.4.3}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
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__(
|
|
@@ -281,6 +281,12 @@ class LazyImgArray(AxesMixin):
|
|
|
281
281
|
chunk_info = ", ".join([f"{s}({o})" for s, o in zip(self.chunksize, self.axes)])
|
|
282
282
|
return chunk_info
|
|
283
283
|
|
|
284
|
+
def astype(self, dtype: DTypeLike, copy: bool = True) -> LazyImgArray:
|
|
285
|
+
"""Convert the array to the specified data type."""
|
|
286
|
+
out = self.__class__(self.value.astype(dtype, copy=copy))
|
|
287
|
+
out._set_info(self)
|
|
288
|
+
return out
|
|
289
|
+
|
|
284
290
|
def _repr_dict_(self):
|
|
285
291
|
return {
|
|
286
292
|
"name": self.name,
|
|
@@ -555,9 +561,9 @@ class LazyImgArray(AxesMixin):
|
|
|
555
561
|
dtype = self.dtype
|
|
556
562
|
all_axes = str(self.axes)
|
|
557
563
|
def _func(input: xp.ndarray, *args, **kwargs):
|
|
558
|
-
out = xp.empty(input.shape,
|
|
564
|
+
out = xp.empty(input.shape, dtype=dtype)
|
|
559
565
|
for sl in iter_slice(input.shape, c_axes, all_axes):
|
|
560
|
-
out[sl] = func(input[sl], *args, **kwargs)
|
|
566
|
+
out[sl] = func(input[sl], *args, **kwargs).astype(dtype, copy=False)
|
|
561
567
|
return out
|
|
562
568
|
depth = switch_slice(c_axes, self.axes, 0, depth)
|
|
563
569
|
return da.map_overlap(
|
|
@@ -1143,10 +1149,11 @@ class LazyImgArray(AxesMixin):
|
|
|
1143
1149
|
@same_dtype(asfloat=True)
|
|
1144
1150
|
@check_input_and_output_lazy
|
|
1145
1151
|
@dims_to_spatial_axes
|
|
1146
|
-
def drift_correction(
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
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:
|
|
1150
1157
|
if along is None:
|
|
1151
1158
|
along = find_first_appeared("tpzcia", include=self.axes, exclude=dims)
|
|
1152
1159
|
elif len(along) != 1:
|
|
@@ -1165,8 +1172,8 @@ class LazyImgArray(AxesMixin):
|
|
|
1165
1172
|
UserWarning)
|
|
1166
1173
|
dims = _dims
|
|
1167
1174
|
elif not isinstance(ref, self.__class__):
|
|
1168
|
-
|
|
1169
|
-
|
|
1175
|
+
ref = self[ref]
|
|
1176
|
+
if ref.axes != along + dims:
|
|
1170
1177
|
raise ValueError(f"Arguments `along`({along}) + `dims`({dims}) do not match "
|
|
1171
1178
|
f"axes of `ref`({ref.axes})")
|
|
1172
1179
|
|
|
@@ -94,9 +94,12 @@ class TiledImage(Generic[_T]):
|
|
|
94
94
|
from dask import array as da
|
|
95
95
|
|
|
96
96
|
input = da.from_array(img.value, chunks=self._chunks)
|
|
97
|
+
|
|
98
|
+
def _func(arr: np.ndarray, *args, **kwargs) -> np.ndarray:
|
|
99
|
+
return func(arr, *args, **kwargs).astype(arr.dtype, copy=False)
|
|
97
100
|
out: np.ndarray = xp.asnumpy(
|
|
98
101
|
da.map_overlap(
|
|
99
|
-
|
|
102
|
+
_func,
|
|
100
103
|
input,
|
|
101
104
|
*args,
|
|
102
105
|
depth=self.depth,
|
|
@@ -114,6 +117,7 @@ class TiledImage(Generic[_T]):
|
|
|
114
117
|
c_axes="",
|
|
115
118
|
depth=self.depth,
|
|
116
119
|
boundary=self.boundary,
|
|
120
|
+
dtype=img.dtype,
|
|
117
121
|
args=args,
|
|
118
122
|
kwargs=kwargs,
|
|
119
123
|
)
|
|
@@ -121,8 +121,11 @@ def array(
|
|
|
121
121
|
dtype = arr.dtype
|
|
122
122
|
|
|
123
123
|
axes, name = _normalize_params(axes, name, like)
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
|
|
125
|
+
if copy:
|
|
126
|
+
_arr = np.array(arr, dtype=dtype)
|
|
127
|
+
else:
|
|
128
|
+
_arr = np.asarray(arr, dtype=dtype)
|
|
126
129
|
|
|
127
130
|
# Automatically determine axes
|
|
128
131
|
if axes is None:
|
|
@@ -567,7 +570,7 @@ def imread(
|
|
|
567
570
|
scale_unit = {a: scale_unit for a in "zyx"}
|
|
568
571
|
|
|
569
572
|
if is_memmap and axes is not None:
|
|
570
|
-
sl = solve_slicer(key, Axes(axes))
|
|
573
|
+
sl = solve_slicer(key, Axes(axes), img.shape)
|
|
571
574
|
axes = "".join(a for a, k in zip(axes, sl) if not isinstance(k, int))
|
|
572
575
|
img = np.asarray(img[sl], dtype=dtype)
|
|
573
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
|