impy-array 2.5.1__tar.gz → 2.5.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.
Files changed (72) hide show
  1. {impy_array-2.5.1 → impy_array-2.5.3}/PKG-INFO +1 -1
  2. {impy_array-2.5.1 → impy_array-2.5.3}/impy/__init__.py +1 -1
  3. {impy_array-2.5.1 → impy_array-2.5.3}/impy/array_api.py +2 -2
  4. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/_utils/_corr.py +3 -3
  5. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/imgarray.py +2 -2
  6. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/labeledarray.py +0 -10
  7. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/lazy.py +0 -10
  8. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/specials.py +32 -33
  9. {impy_array-2.5.1 → impy_array-2.5.3}/impy/core.py +29 -7
  10. {impy_array-2.5.1 → impy_array-2.5.3}/impy/frame/frames.py +49 -60
  11. {impy_array-2.5.1 → impy_array-2.5.3}/impy/io/_zarr.py +2 -4
  12. {impy_array-2.5.1 → impy_array-2.5.3}/impy/viewer/utils.py +70 -73
  13. {impy_array-2.5.1 → impy_array-2.5.3}/impy/viewer/viewer.py +107 -109
  14. {impy_array-2.5.1 → impy_array-2.5.3}/.gitignore +0 -0
  15. {impy_array-2.5.1 → impy_array-2.5.3}/LICENSE +0 -0
  16. {impy_array-2.5.1 → impy_array-2.5.3}/README.md +0 -0
  17. {impy_array-2.5.1 → impy_array-2.5.3}/impy/__main__.py +0 -0
  18. {impy_array-2.5.1 → impy_array-2.5.3}/impy/_const.py +0 -0
  19. {impy_array-2.5.1 → impy_array-2.5.3}/impy/_types.py +0 -0
  20. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/__init__.py +0 -0
  21. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/_imshow.py +0 -0
  22. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/_utils/__init__.py +0 -0
  23. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/_utils/_deconv.py +0 -0
  24. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/_utils/_docs.py +0 -0
  25. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/_utils/_filters.py +0 -0
  26. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/_utils/_glcm.py +0 -0
  27. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/_utils/_linalg.py +0 -0
  28. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/_utils/_misc.py +0 -0
  29. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/_utils/_plot.py +0 -0
  30. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/_utils/_process_numba.py +0 -0
  31. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/_utils/_skimage.py +0 -0
  32. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/_utils/_structures.py +0 -0
  33. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/_utils/_transform.py +0 -0
  34. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/axesmixin.py +0 -0
  35. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/bases/__init__.py +0 -0
  36. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/bases/metaarray.py +0 -0
  37. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/bases/overload.py +0 -0
  38. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/big.py +0 -0
  39. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/label.py +0 -0
  40. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/phasearray.py +0 -0
  41. {impy_array-2.5.1 → impy_array-2.5.3}/impy/arrays/tiled.py +0 -0
  42. {impy_array-2.5.1 → impy_array-2.5.3}/impy/axes/__init__.py +0 -0
  43. {impy_array-2.5.1 → impy_array-2.5.3}/impy/axes/_axes.py +0 -0
  44. {impy_array-2.5.1 → impy_array-2.5.3}/impy/axes/_axes_tuple.py +0 -0
  45. {impy_array-2.5.1 → impy_array-2.5.3}/impy/axes/_axis.py +0 -0
  46. {impy_array-2.5.1 → impy_array-2.5.3}/impy/axes/_slicer.py +0 -0
  47. {impy_array-2.5.1 → impy_array-2.5.3}/impy/binder.py +0 -0
  48. {impy_array-2.5.1 → impy_array-2.5.3}/impy/collections.py +0 -0
  49. {impy_array-2.5.1 → impy_array-2.5.3}/impy/correlation.py +0 -0
  50. {impy_array-2.5.1 → impy_array-2.5.3}/impy/frame/__init__.py +0 -0
  51. {impy_array-2.5.1 → impy_array-2.5.3}/impy/io/__init__.py +0 -0
  52. {impy_array-2.5.1 → impy_array-2.5.3}/impy/io/_czi.py +0 -0
  53. {impy_array-2.5.1 → impy_array-2.5.3}/impy/io/_mrc.py +0 -0
  54. {impy_array-2.5.1 → impy_array-2.5.3}/impy/io/_nd2.py +0 -0
  55. {impy_array-2.5.1 → impy_array-2.5.3}/impy/io/_npy.py +0 -0
  56. {impy_array-2.5.1 → impy_array-2.5.3}/impy/io/_registry.py +0 -0
  57. {impy_array-2.5.1 → impy_array-2.5.3}/impy/io/_tiff.py +0 -0
  58. {impy_array-2.5.1 → impy_array-2.5.3}/impy/io/_utils.py +0 -0
  59. {impy_array-2.5.1 → impy_array-2.5.3}/impy/lazy/__init__.py +0 -0
  60. {impy_array-2.5.1 → impy_array-2.5.3}/impy/lazy/core.py +0 -0
  61. {impy_array-2.5.1 → impy_array-2.5.3}/impy/lazy/random.py +0 -0
  62. {impy_array-2.5.1 → impy_array-2.5.3}/impy/random.py +0 -0
  63. {impy_array-2.5.1 → impy_array-2.5.3}/impy/roi.py +0 -0
  64. {impy_array-2.5.1 → impy_array-2.5.3}/impy/utils/__init__.py +0 -0
  65. {impy_array-2.5.1 → impy_array-2.5.3}/impy/utils/axesop.py +0 -0
  66. {impy_array-2.5.1 → impy_array-2.5.3}/impy/utils/deco.py +0 -0
  67. {impy_array-2.5.1 → impy_array-2.5.3}/impy/utils/gauss.py +0 -0
  68. {impy_array-2.5.1 → impy_array-2.5.3}/impy/utils/misc.py +0 -0
  69. {impy_array-2.5.1 → impy_array-2.5.3}/impy/utils/slicer.py +0 -0
  70. {impy_array-2.5.1 → impy_array-2.5.3}/impy/utils/utilcls.py +0 -0
  71. {impy_array-2.5.1 → impy_array-2.5.3}/impy/viewer/__init__.py +0 -0
  72. {impy_array-2.5.1 → impy_array-2.5.3}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: impy-array
3
- Version: 2.5.1
3
+ Version: 2.5.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>
@@ -1,4 +1,4 @@
1
- __version__ = "2.5.1"
1
+ __version__ = "2.5.3"
2
2
  __author__ = "Hanjin Liu"
3
3
  __email__ = "liuhanjin-sc@g.ecc.u-tokyo.ac.jp"
4
4
 
@@ -97,7 +97,7 @@ class XP:
97
97
  self.angle = np.angle
98
98
  self.abs = np.abs
99
99
  self.mod = np.mod
100
- self.fix = np.fix
100
+ self.trunc = np.trunc
101
101
  self.round = np.round
102
102
  self.gradient = np.gradient
103
103
  self.tensordot = np.tensordot
@@ -165,7 +165,7 @@ class XP:
165
165
  self.angle = cp.angle
166
166
  self.abs = cp.abs
167
167
  self.mod = cp.mod
168
- self.fix = cp.fix
168
+ self.trunc = cp.trunc
169
169
  self.round = cp.round
170
170
  try:
171
171
  self.gradient = cp.gradient
@@ -38,16 +38,16 @@ def subpixel_pcc(
38
38
  ):
39
39
  power, product = _pcc_and_power_spec(f0, f1, max_shifts)
40
40
  maxima = xp.unravel_index(xp.argmax(power), power.shape)
41
- midpoints = xp.array([np.fix(axis_size / 2) for axis_size in power.shape])
41
+ midpoints = xp.array([np.trunc(axis_size / 2) for axis_size in power.shape])
42
42
 
43
43
  shifts = xp.asarray(maxima, dtype=np.float32)
44
44
  shifts[shifts > midpoints] -= xp.array(power.shape)[shifts > midpoints]
45
45
  # Initial shift estimate in upsampled grid
46
- shifts = xp.fix(shifts * upsample_factor) / upsample_factor
46
+ shifts = xp.trunc(shifts * upsample_factor) / upsample_factor
47
47
  if upsample_factor > 1:
48
48
  upsampled_region_size = np.ceil(upsample_factor * 1.5)
49
49
  # Center of output array at dftshift + 1
50
- dftshift = xp.fix(upsampled_region_size / 2.0)
50
+ dftshift = xp.trunc(upsampled_region_size / 2.0)
51
51
  upsample_factor = xp.array(upsample_factor, dtype=np.float32)
52
52
  # Matrix multiply DFT around the current shift estimate
53
53
  sample_region_offset = dftshift - shifts*upsample_factor
@@ -2675,7 +2675,7 @@ class ImgArray(LabeledArray):
2675
2675
  out = pd.concat(out, axis=0)
2676
2676
 
2677
2677
  out = MarkerFrame(out.reindex(columns=list(coords._axes)),
2678
- columns=str(coords._axes), dtype=np.float32).as_standard_type()
2678
+ columns=str(coords._axes), dtype=np.float32)
2679
2679
  out.set_scale(coords.scale)
2680
2680
 
2681
2681
  return out
@@ -3801,7 +3801,7 @@ class ImgArray(LabeledArray):
3801
3801
 
3802
3802
  def regionprops(
3803
3803
  self,
3804
- properties: Iterable[str] | str = ("mean_intensity",),
3804
+ properties: Iterable[str] | str = ("intensity_mean",),
3805
3805
  *,
3806
3806
  extra_properties: Iterable[Callable] | None = None,
3807
3807
  ) -> DataDict[str, PropArray]:
@@ -302,16 +302,6 @@ class LabeledArray(MetaArray):
302
302
  ext = ".tif"
303
303
  save_path = save_path.parent / (save_path.name + ext)
304
304
 
305
- if not Path(save_path).is_absolute():
306
- if self.source is None:
307
- raise ValueError(
308
- "Image directory path is unknown. Set by \n"
309
- " >>> img.source = \"...\"\n"
310
- "or specify absolute path like\n"
311
- " >>> img.imsave(\"/path/to/XXX.tif\")"
312
- )
313
- save_path = self.source.parent / save_path
314
-
315
305
  if not overwrite and save_path.exists():
316
306
  raise FileExistsError(f"File {save_path!r} already exists.")
317
307
  if self.metadata is None:
@@ -352,16 +352,6 @@ class LazyImgArray(AxesMixin):
352
352
  ext = ".tif"
353
353
  save_path = save_path.parent / (save_path.name + ext)
354
354
 
355
- if not Path(save_path).is_absolute():
356
- if self.source is None:
357
- raise ValueError(
358
- "Image directory path is unknown. Set by \n"
359
- " >>> img.source = \"...\"\n"
360
- "or specify absolute path like\n"
361
- " >>> img.imsave(\"/path/to/XXX.tif\")"
362
- )
363
- save_path = self.source.parent / save_path
364
-
365
355
  if not overwrite and save_path.exists():
366
356
  raise FileExistsError(f"File {save_path!r} already exists.")
367
357
  if dtype is None:
@@ -20,23 +20,23 @@ SCALAR_PROP = (
20
20
  class PropArray(MetaArray):
21
21
  additional_props = ["_source", "_metadata", "_name", "propname"]
22
22
  propname: str
23
- def __new__(cls, obj, *, name=None, axes=None, source=None,
23
+ def __new__(cls, obj, *, name=None, axes=None, source=None,
24
24
  metadata=None, propname=None, dtype=None):
25
25
  if propname is None:
26
26
  propname = "User_Defined"
27
-
27
+
28
28
  if dtype is None and propname in SCALAR_PROP:
29
29
  dtype = np.float32
30
30
  elif dtype is None:
31
31
  dtype = object
32
32
  else:
33
33
  dtype = dtype
34
-
34
+
35
35
  self = super().__new__(cls, obj, name, axes, source, metadata, dtype=dtype)
36
36
  self.propname = propname
37
-
37
+
38
38
  return self
39
-
39
+
40
40
  def _repr_dict_(self):
41
41
  return {
42
42
  "name": self.name,
@@ -44,7 +44,7 @@ class PropArray(MetaArray):
44
44
  "dtype": self.dtype,
45
45
  "source": self.source,
46
46
  "property name": self.propname}
47
-
47
+
48
48
  def plot(self, along=None, cmap="jet", cmap_range=(0, 1)):
49
49
  """
50
50
  Plot all the results with x-axis defined by `along`.
@@ -57,7 +57,7 @@ class PropArray(MetaArray):
57
57
  Colormap of each graph.
58
58
  cmap_range : tuple, default is (0, 1)
59
59
  Range of float for colormap iteration.
60
- """
60
+ """
61
61
  import matplotlib.pyplot as plt
62
62
  if self.dtype == object:
63
63
  raise TypeError(f"Cannot call plot_profile for {self.propname} "
@@ -67,21 +67,21 @@ class PropArray(MetaArray):
67
67
  along = find_first_appeared("tzp<yxc", include=self.axes)
68
68
  except ValueError:
69
69
  along = self.axes[-1]
70
-
70
+
71
71
  iteraxes = self.axes.drop(along)
72
72
  cmap = plt.get_cmap(cmap)
73
73
  positions = np.linspace(*cmap_range, self.size//self.sizeof(along), endpoint=False)
74
74
  x = np.arange(self.sizeof(along))*self.scale[along]
75
75
  for i, (sl, y) in enumerate(self.iter(iteraxes)):
76
76
  plt.plot(x, y, color=cmap(positions[i]))
77
-
77
+
78
78
  plt.title(f"{self.propname}")
79
79
  plt.xlabel(along)
80
80
  plt.show()
81
-
81
+
82
82
  return self
83
-
84
- def hist(self, along: str = "N", bins: int = None, cmap: str = "jet",
83
+
84
+ def hist(self, along: str = "N", bins: int = None, cmap: str = "jet",
85
85
  cmap_range: tuple[float, float] = (0., 1.)) -> PropArray:
86
86
  """
87
87
  Plot histogram.
@@ -96,31 +96,31 @@ class PropArray(MetaArray):
96
96
  Colormap of each graph.
97
97
  cmap_range : tuple, default is (0, 1)
98
98
  Range of float for colormap iteration.
99
-
99
+
100
100
  Returns
101
101
  -------
102
102
  self
103
- """
103
+ """
104
104
  import matplotlib.pyplot as plt
105
105
 
106
106
  if self.dtype == object:
107
107
  raise TypeError(f"Cannot call plot_profile for {self.propname} "
108
108
  "because dtype == object.")
109
-
109
+
110
110
  if along is None:
111
111
  along = find_first_appeared("pxyzt<c", include=self.axes)
112
-
112
+
113
113
  iteraxes = self.axes.drop(along)
114
114
  cmap = plt.get_cmap(cmap)
115
115
  positions = np.linspace(*cmap_range, self.size//self.sizeof(along), endpoint=False)
116
116
  for i, (sl, y) in enumerate(self.iter(iteraxes)):
117
117
  plt.hist(y, color=cmap(positions[i]), bins=bins, alpha=0.5)
118
-
118
+
119
119
  plt.title(f"{self.propname}")
120
120
  plt.show()
121
-
121
+
122
122
  return self
123
-
123
+
124
124
  def curve_fit(self, f: Callable, p0 = None, dims = "t", return_fit: bool = True) -> DataDict[str, PropArray]:
125
125
  """
126
126
  Run scipy.optimize.curve_fit for each dimesion.
@@ -130,12 +130,12 @@ class PropArray(MetaArray):
130
130
  f : callable
131
131
  Model function.
132
132
  p0 : array or callable, optional
133
- Initial parameter. Callable object that estimate initial paramter can also
133
+ Initial parameter. Callable object that estimate initial paramter can also
134
134
  be passed here.
135
135
  dims : str, by default "t"
136
136
  Along which axes fitting algorithms are conducted.
137
137
  return_fit : bool, by default True
138
- If fitting trajectories are returned. If the input array is large, this will
138
+ If fitting trajectories are returned. If the input array is large, this will
139
139
  save much memory.
140
140
 
141
141
  Returns
@@ -144,23 +144,23 @@ class PropArray(MetaArray):
144
144
  params : Fitting parameters
145
145
  covs : Covariances
146
146
  fit : fitting trajectories (if return_fit==True)
147
- """
147
+ """
148
148
  from scipy import optimize as opt
149
149
 
150
150
  c_axes = complement_axes(dims, all_axes=self.axes)
151
-
151
+
152
152
  if len(dims)!=1:
153
153
  raise NotImplementedError("Only 1-dimensional fitting is implemented.")
154
-
154
+
155
155
  n_params = len(signature(f).parameters)-1
156
-
156
+
157
157
  params = np.empty(self.sizesof(c_axes) + (n_params,), dtype=np.float32)
158
158
  errs = np.empty(self.sizesof(c_axes) + (n_params,), dtype=np.float32)
159
159
  if return_fit:
160
160
  fit = np.empty(self.sizesof(c_axes) + (self.sizeof(dims),), dtype=self.dtype)
161
-
161
+
162
162
  xdata = np.arange(self.sizeof(dims))*self.scale[dims]
163
-
163
+
164
164
  for sl, data in self.iter(c_axes, exclude=dims):
165
165
  p0_ = p0 if not callable(p0) else p0(data)
166
166
  result = opt.curve_fit(f, xdata, data, p0_)
@@ -168,22 +168,22 @@ class PropArray(MetaArray):
168
168
  errs[sl] = np.sqrt(np.diag(cov))
169
169
  if return_fit:
170
170
  fit[sl] = f(xdata, *(result[0]))
171
-
171
+
172
172
  # set infos
173
173
  params = params.view(self.__class__)
174
174
  errs = errs.view(self.__class__)
175
175
  params._set_info(self, new_axes=self.axes.drop(dims).extend("m"))
176
176
  errs._set_info(self, new_axes=self.axes.drop(dims).extend("m"))
177
-
177
+
178
178
  if return_fit:
179
179
  fit = fit.view(self.__class__)
180
180
  fit._set_info(self, new_axes=self.axes.drop(dims).extend(dims))
181
-
181
+
182
182
  if return_fit:
183
183
  return DataDict(params=params, errs=errs, fit=fit)
184
184
  else:
185
185
  return DataDict(params=params, errs=errs)
186
-
186
+
187
187
  def as_frame(self, colname: str = "f") -> AxesFrame:
188
188
  """
189
189
  N-dimensional data to DataFrame. The intensity data is stored in the `colname` column.
@@ -197,7 +197,7 @@ class PropArray(MetaArray):
197
197
  -------
198
198
  AxesFrame
199
199
  DataFrame with PropArray data.
200
- """
200
+ """
201
201
  from ..frame import AxesFrame
202
202
  if colname in self.axes:
203
203
  raise ImageAxesError(f"Axis {colname} already exists.")
@@ -210,4 +210,3 @@ class PropArray(MetaArray):
210
210
  df = AxesFrame(data_dict, columns=new_axes)
211
211
  df.set_scale(self)
212
212
  return df
213
-
@@ -1,4 +1,5 @@
1
1
  from __future__ import annotations
2
+ from contextlib import suppress
2
3
  import os
3
4
  from pathlib import Path
4
5
  import re
@@ -489,12 +490,12 @@ def broadcast_arrays(*arrays: MetaArray) -> list[MetaArray]:
489
490
  out = [a.broadcast_to(shape, axes) for a in arrays]
490
491
  return out
491
492
 
492
- # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
493
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
493
494
  # Imread functions
494
- # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
495
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
495
496
 
496
497
  def imread(
497
- path: str,
498
+ path: str | Path,
498
499
  dtype: DTypeLike = None,
499
500
  key: AxesTargetedSlicer | None = None,
500
501
  *,
@@ -502,6 +503,7 @@ def imread(
502
503
  squeeze: bool = False,
503
504
  ) -> ImgArray:
504
505
  """Load image(s) from a path.
506
+
505
507
  You can read list of images from directories with wildcards or ``"$"`` in ``path``.
506
508
 
507
509
  Parameters
@@ -535,12 +537,30 @@ def imread(
535
537
  Wall time: 3.01 ms
536
538
 
537
539
  """
538
- path = str(Path(path).expanduser())
540
+ if str(path).startswith("~"):
541
+ path = str(Path(path).expanduser())
542
+ else:
543
+ path = str(path)
544
+
545
+ # if a directory of a ome.tiff is given, read the first image (which will eventually
546
+ # read all the positions) and use the directory name as source/name.
547
+ if (_fp := Path(path)).is_dir() and _fp.suffix != ".zarr":
548
+ _first_tif = next(_fp.glob("*.ome.tif"), None)
549
+ if _first_tif is None:
550
+ raise ValueError(
551
+ "If a directory is given to imread, only directory of ome.tif is "
552
+ "supported. Please use wildcard like `path/to/dir/*.tif` to read "
553
+ "images in a directory."
554
+ )
555
+ out = imread(_first_tif, dtype=dtype, key=key, name=_fp.name, squeeze=squeeze)
556
+ out.source = _fp
557
+ return out
558
+
539
559
  is_memmap = (key is not None)
540
560
 
541
561
  if "$" in path:
542
562
  return _imread_stack(path, dtype=dtype, key=key, squeeze=squeeze)
543
- elif "*" in path:
563
+ elif "*" in path or "?" in path or "[" in path:
544
564
  return _imread_glob(path, dtype=dtype, key=key, squeeze=squeeze)
545
565
  elif not Path(path).exists():
546
566
  raise FileNotFoundError(f"No such file or directory: {path}")
@@ -575,7 +595,8 @@ def imread(
575
595
  # In case the image is in yxc-order. This sometimes happens.
576
596
  self: ImgArray = np.moveaxis(self, -1, -3)
577
597
  if labels is not None:
578
- self.set_axis_label(c=labels)
598
+ with suppress(ValueError):
599
+ self.set_axis_label(c=labels)
579
600
 
580
601
  if dtype is None:
581
602
  dtype = self.dtype
@@ -619,8 +640,9 @@ def _imread_glob(path: str, squeeze: bool = False, **kwargs) -> ImgArray:
619
640
  out: ImgArray = np.stack(imgs, axis="p")
620
641
  if squeeze:
621
642
  out = np.squeeze(out)
643
+ sep = "*" if "*" in path else "?"
622
644
  try:
623
- base = os.path.split(path.split("*")[0])[0]
645
+ base = os.path.split(path.split(sep)[0])[0]
624
646
  out.source = base
625
647
  except Exception:
626
648
  pass
@@ -18,11 +18,11 @@ else:
18
18
 
19
19
  class AxesFrame(pd.DataFrame):
20
20
  _metadata = ["_axes"]
21
-
21
+
22
22
  @property
23
23
  def _constructor(self):
24
24
  return AxesFrame
25
-
25
+
26
26
  def __init__(self, data=None, columns=None, **kwargs):
27
27
  if isinstance(columns, (str, Axes)):
28
28
  kwargs["columns"] = [a for a in columns]
@@ -32,17 +32,17 @@ class AxesFrame(pd.DataFrame):
32
32
  columns = str(data._axes)
33
33
  else:
34
34
  kwargs["columns"] = columns
35
-
35
+
36
36
  super().__init__(data, **kwargs)
37
37
  self._axes = Axes(self.columns)
38
-
38
+
39
39
  def _get_coords_cols(self):
40
40
  return [a for a in self.columns if len(a) == 1]
41
-
41
+
42
42
  def get_coords(self):
43
43
  return self[self.columns[self.columns.isin([a for a in self.columns if len(a) == 1])]]
44
-
45
-
44
+
45
+
46
46
  def __getitem__(self, k):
47
47
  # sl_tuple = solve_slicer(key, self.axes)
48
48
  # drops: list[int] = []
@@ -56,7 +56,7 @@ class AxesFrame(pd.DataFrame):
56
56
  # binary = np.logical_and(binary, (arr - s0) % step == 0)
57
57
  # elif step < 0:
58
58
  # raise NotImplementedError
59
-
59
+
60
60
  # else:
61
61
  # binary = arr == sl
62
62
  # drops.append(i)
@@ -72,9 +72,9 @@ class AxesFrame(pd.DataFrame):
72
72
  for each in k.split(";"):
73
73
  self = self.__getitem__(each.strip())
74
74
  return self
75
-
75
+
76
76
  elif "=" in k:
77
- axis, sl = [a.strip() for a in k.split("=")]
77
+ axis, sl = (a.strip() for a in k.split("="))
78
78
  sl = str_to_slice(sl)
79
79
  if isinstance(sl, int):
80
80
  out = self[self[axis]==sl]
@@ -87,19 +87,19 @@ class AxesFrame(pd.DataFrame):
87
87
  elif "" == k:
88
88
  return self
89
89
  else:
90
- out = super().__getitem__(k)
90
+ out = super().__getitem__(k)
91
91
  else:
92
92
  out = super().__getitem__(k)
93
-
93
+
94
94
  if isinstance(out, AxesFrame):
95
95
  out._axes = Axes(out._get_coords_cols())
96
96
  out.set_scale(self)
97
97
  return out
98
-
98
+
99
99
  @property
100
100
  def col_axes(self):
101
101
  return self._axes
102
-
102
+
103
103
  @col_axes.setter
104
104
  def col_axes(self, value):
105
105
  naxes = self.shape[1]
@@ -113,11 +113,11 @@ class AxesFrame(pd.DataFrame):
113
113
  f"array (ndim={naxes}) and axes ({value})"
114
114
  )
115
115
  self._axes = axes
116
-
116
+
117
117
  @property
118
118
  def scale(self):
119
119
  return self._axes.scale
120
-
120
+
121
121
  def set_scale(self, other=None, **kwargs) -> None:
122
122
  """
123
123
  Set scales of each axis.
@@ -127,7 +127,7 @@ class AxesFrame(pd.DataFrame):
127
127
  other : dict, AxesFrame or MetaArray, optional
128
128
  New scales. If dict, it should be like {"x": 0.1, "y": 0.1}. If MetaArray, only
129
129
  scales of common axes are copied.
130
- kwargs :
130
+ kwargs :
131
131
  This enables function call like set_scale(x=0.1, y=0.1).
132
132
 
133
133
  """
@@ -142,40 +142,29 @@ class AxesFrame(pd.DataFrame):
142
142
  # check if all the keys are contained in axes.
143
143
  for a, val in other.items():
144
144
  if a not in self._axes:
145
- raise ImageAxesError(f"Image does not have axis {a}.")
145
+ raise ImageAxesError(f"Image does not have axis {a}.")
146
146
  elif not np.isscalar(val):
147
147
  raise TypeError(f"Cannot set non-numeric value as scales.")
148
-
148
+
149
149
  for k, v in other.items():
150
150
  self.col_axes[k].scale = v
151
-
151
+
152
152
  elif hasattr(other, "scale"):
153
153
  self.set_scale({a: s for a, s in other.scale.items() if a in self._axes})
154
-
154
+
155
155
  elif kwargs:
156
156
  self.set_scale(dict(kwargs))
157
-
157
+
158
158
  else:
159
159
  raise TypeError(f"'other' must be str or MetaArray, but got {type(other)}")
160
-
160
+
161
161
  return None
162
-
163
- def as_standard_type(self) -> AxesFrame:
164
- """
165
- t or c -> uint16
166
- p -> uint32
167
- z, y, x -> float32
168
- """
169
- dtype = lambda a: np.uint16 if a in ["t", "c"] else (np.uint32 if a == Const["ID_AXIS"] else np.float32)
170
- out = self.__class__(self.astype({a: dtype(a) for a in self.col_axes}))
171
- out._axes = self._axes
172
- return out
173
-
174
-
162
+
163
+
175
164
  def split(self, axis="c") -> list[AxesFrame]:
176
165
  """
177
166
  Split DataFrame according to its indices. For example, if self is an DataFrame with columns
178
- "t", "c", "y", "x" and it is split along "c" axis, then output is a list of DataFrame with
167
+ "t", "c", "y", "x" and it is split along "c" axis, then output is a list of DataFrame with
179
168
  columns "t", "y", "x".
180
169
 
181
170
  Parameters
@@ -204,7 +193,7 @@ class AxesFrame(pd.DataFrame):
204
193
  ----------
205
194
  axes : str
206
195
  Along which axes to iterate.
207
-
196
+
208
197
  Yields
209
198
  -------
210
199
  tuple and AxesFrame
@@ -214,10 +203,10 @@ class AxesFrame(pd.DataFrame):
214
203
  outsl = [slice(None)] * len(self.col_axes)
215
204
  cols = [a for a in self.col_axes if a not in axes]
216
205
  groupkeys = [a for a in axes]
217
-
206
+
218
207
  if len(groupkeys) == 0:
219
208
  yield (slice(None),), self
220
-
209
+
221
210
  else:
222
211
  if len(groupkeys) == 1:
223
212
  groupkeys = groupkeys[0]
@@ -227,21 +216,21 @@ class AxesFrame(pd.DataFrame):
227
216
  sl = (sl,)
228
217
  [outsl.__setitem__(i, s) for i, s in zip(indices, sl)]
229
218
  yield tuple(outsl), af
230
-
219
+
231
220
  def sort(self):
232
221
  ids = self._axes.argsort()
233
222
  return self[[self._axes[i] for i in ids]]
234
-
223
+
235
224
  def proj(self, axis=None):
236
225
  if axis is None:
237
226
  axis = complement_axes("yx", self._axes)
238
227
  cols = [a for a in self.col_axes if a not in axis]
239
228
  return self[cols]
240
-
229
+
241
230
  def tp_no_verbose(func):
242
231
  """
243
232
  Temporary suppress logging in trackpy.
244
- """
233
+ """
245
234
  @wraps(func)
246
235
  def wrapper(self, *args, **kwargs):
247
236
  tp.quiet(suppress=True)
@@ -277,19 +266,19 @@ class MarkerFrame(AxesFrame):
277
266
  -------
278
267
  TrackFrame
279
268
  Result of particle tracking.
280
- """
281
-
269
+ """
270
+
282
271
  linked = tp.link(pd.DataFrame(self), search_range=search_range, t_column="t", memory=memory, **kwargs)
283
-
272
+
284
273
  linked.rename(columns = {"particle":Const["ID_AXIS"]}, inplace=True)
285
274
  linked = linked.reindex(columns=[a for a in Const["ID_AXIS"]+str(self.col_axes)])
286
-
275
+
287
276
  track = TrackFrame(linked, columns="".join(linked.columns.tolist()))
288
277
  track.set_scale(self)
289
278
  if min_dwell > 0:
290
279
  out = track.filter_stubs(min_dwell)
291
280
  else:
292
- out = track.as_standard_type()
281
+ out = track
293
282
  out.index = np.arange(len(out))
294
283
  return out
295
284
 
@@ -298,7 +287,7 @@ class TrackFrame(AxesFrame):
298
287
  df = pd.DataFrame(self, copy=True, dtype=np.float32)
299
288
  df.rename(columns = {"t":"frame", Const["ID_AXIS"]:"particle"}, inplace=True)
300
289
  return df
301
-
290
+
302
291
  @tp_no_verbose
303
292
  def track_drift(self, smoothing=0):
304
293
  df = self._renamed_df()
@@ -307,25 +296,25 @@ class TrackFrame(AxesFrame):
307
296
  ori = pd.DataFrame({"y":[0.], "x":[0.]}, dtype=np.float32)
308
297
  shift = pd.concat([ori, shift], axis=0)
309
298
  return MarkerFrame(shift)
310
-
299
+
311
300
  @tp_no_verbose
312
301
  def msd(self, max_lagt:int=100, detail:bool=False):
313
302
  df = self._renamed_df()
314
- return tp.motion.msd(df, self.scale["x"], self.scale["t"],
303
+ return tp.motion.msd(df, self.scale["x"], self.scale["t"],
315
304
  max_lagtime=max_lagt, detail=detail)
316
-
305
+
317
306
  @tp_no_verbose
318
307
  def imsd(self, max_lagt=100):
319
308
  df = self._renamed_df()
320
- return tp.motion.imsd(df, self.scale["x"], self.scale["t"],
309
+ return tp.motion.imsd(df, self.scale["x"], self.scale["t"],
321
310
  max_lagtime=max_lagt)
322
-
311
+
323
312
  @tp_no_verbose
324
313
  def emsd(self, max_lagt=100, detail=False):
325
314
  df = self._renamed_df()
326
- return tp.motion.emsd(df, self.scale["x"], self.scale["t"],
315
+ return tp.motion.emsd(df, self.scale["x"], self.scale["t"],
327
316
  max_lagtime=max_lagt, detail=detail)
328
-
317
+
329
318
  @tp_no_verbose
330
319
  def filter_stubs(self, min_dwell=3):
331
320
  df = self._renamed_df()
@@ -334,8 +323,8 @@ class TrackFrame(AxesFrame):
334
323
  df = df.astype({"t":np.uint16, Const["ID_AXIS"]:np.uint32})
335
324
  out = TrackFrame(df, columns=self.col_axes)
336
325
  out.set_scale(self)
337
- return out.as_standard_type()
338
-
326
+ return out
327
+
339
328
  def as_path(self):
340
329
  df = self[[a for a in self._axes if a != "t"]]
341
330
  return PathFrame(df, columns=df._axes)
@@ -346,4 +335,4 @@ class PathFrame(AxesFrame):
346
335
  dims = [a for a in "zyx" if a in self._axes]
347
336
  path_len = lambda df: np.sum(np.sqrt(sum(np.diff(df[a])**2 for a in dims)))
348
337
  c_axes = complement_axes(dims, self._axes)
349
- return self.groupby(list(c_axes)).apply(path_len)
338
+ return self.groupby(list(c_axes)).apply(path_len)