pytme 0.2.0b0__cp311-cp311-macosx_14_0_arm64.whl → 0.2.2__cp311-cp311-macosx_14_0_arm64.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.
Files changed (52) hide show
  1. pytme-0.2.2.data/scripts/match_template.py +1187 -0
  2. {pytme-0.2.0b0.data → pytme-0.2.2.data}/scripts/postprocess.py +170 -71
  3. {pytme-0.2.0b0.data → pytme-0.2.2.data}/scripts/preprocessor_gui.py +179 -86
  4. pytme-0.2.2.dist-info/METADATA +91 -0
  5. pytme-0.2.2.dist-info/RECORD +74 -0
  6. {pytme-0.2.0b0.dist-info → pytme-0.2.2.dist-info}/WHEEL +1 -1
  7. scripts/extract_candidates.py +126 -87
  8. scripts/match_template.py +596 -209
  9. scripts/match_template_filters.py +571 -223
  10. scripts/postprocess.py +170 -71
  11. scripts/preprocessor_gui.py +179 -86
  12. scripts/refine_matches.py +567 -159
  13. tme/__init__.py +0 -1
  14. tme/__version__.py +1 -1
  15. tme/analyzer.py +627 -855
  16. tme/backends/__init__.py +41 -11
  17. tme/backends/_jax_utils.py +185 -0
  18. tme/backends/cupy_backend.py +120 -225
  19. tme/backends/jax_backend.py +282 -0
  20. tme/backends/matching_backend.py +464 -388
  21. tme/backends/mlx_backend.py +45 -68
  22. tme/backends/npfftw_backend.py +256 -514
  23. tme/backends/pytorch_backend.py +41 -154
  24. tme/density.py +312 -421
  25. tme/extensions.cpython-311-darwin.so +0 -0
  26. tme/matching_data.py +366 -303
  27. tme/matching_exhaustive.py +279 -1521
  28. tme/matching_optimization.py +234 -129
  29. tme/matching_scores.py +884 -0
  30. tme/matching_utils.py +281 -387
  31. tme/memory.py +377 -0
  32. tme/orientations.py +226 -66
  33. tme/parser.py +3 -4
  34. tme/preprocessing/__init__.py +2 -0
  35. tme/preprocessing/_utils.py +217 -0
  36. tme/preprocessing/composable_filter.py +31 -0
  37. tme/preprocessing/compose.py +55 -0
  38. tme/preprocessing/frequency_filters.py +388 -0
  39. tme/preprocessing/tilt_series.py +1011 -0
  40. tme/preprocessor.py +574 -530
  41. tme/structure.py +495 -189
  42. tme/types.py +5 -3
  43. pytme-0.2.0b0.data/scripts/match_template.py +0 -800
  44. pytme-0.2.0b0.dist-info/METADATA +0 -73
  45. pytme-0.2.0b0.dist-info/RECORD +0 -66
  46. tme/helpers.py +0 -881
  47. tme/matching_constrained.py +0 -195
  48. {pytme-0.2.0b0.data → pytme-0.2.2.data}/scripts/estimate_ram_usage.py +0 -0
  49. {pytme-0.2.0b0.data → pytme-0.2.2.data}/scripts/preprocess.py +0 -0
  50. {pytme-0.2.0b0.dist-info → pytme-0.2.2.dist-info}/LICENSE +0 -0
  51. {pytme-0.2.0b0.dist-info → pytme-0.2.2.dist-info}/entry_points.txt +0 -0
  52. {pytme-0.2.0b0.dist-info → pytme-0.2.2.dist-info}/top_level.txt +0 -0
@@ -1,59 +1,87 @@
1
- """ Backend using cupy and GPU acceleration for
2
- template matching.
1
+ """ Backend using cupy for template matching.
3
2
 
4
3
  Copyright (c) 2023 European Molecular Biology Laboratory
5
4
 
6
5
  Author: Valentin Maurer <valentin.maurer@embl-hamburg.de>
7
6
  """
8
-
9
7
  import warnings
10
- from typing import Tuple, Dict, Callable
8
+ from importlib.util import find_spec
11
9
  from contextlib import contextmanager
10
+ from typing import Tuple, Callable, List
12
11
 
13
12
  import numpy as np
14
- from numpy.typing import NDArray
15
13
 
16
14
  from .npfftw_backend import NumpyFFTWBackend
17
- from ..types import CupyArray
15
+ from ..types import CupyArray, NDArray, shm_type
18
16
 
19
17
  PLAN_CACHE = {}
18
+ TEXTURE_CACHE = {}
20
19
 
21
20
 
22
21
  class CupyBackend(NumpyFFTWBackend):
23
22
  """
24
- A cupy based backend for template matching
23
+ A cupy-based matching backend.
25
24
  """
26
25
 
27
26
  def __init__(
28
- self, default_dtype=None, complex_dtype=None, default_dtype_int=None, **kwargs
27
+ self,
28
+ float_dtype: type = None,
29
+ complex_dtype: type = None,
30
+ int_dtype: type = None,
31
+ overflow_safe_dtype: type = None,
32
+ **kwargs,
29
33
  ):
30
34
  import cupy as cp
31
35
  from cupyx.scipy.fft import get_fft_plan
32
36
  from cupyx.scipy.ndimage import affine_transform
33
37
  from cupyx.scipy.ndimage import maximum_filter
34
38
 
35
- default_dtype = cp.float32 if default_dtype is None else default_dtype
39
+ float_dtype = cp.float32 if float_dtype is None else float_dtype
36
40
  complex_dtype = cp.complex64 if complex_dtype is None else complex_dtype
37
- default_dtype_int = cp.int32 if default_dtype_int is None else default_dtype_int
41
+ int_dtype = cp.int32 if int_dtype is None else int_dtype
42
+ if overflow_safe_dtype is None:
43
+ overflow_safe_dtype = cp.float32
38
44
 
39
45
  super().__init__(
40
46
  array_backend=cp,
41
- default_dtype=default_dtype,
47
+ float_dtype=float_dtype,
42
48
  complex_dtype=complex_dtype,
43
- default_dtype_int=default_dtype_int,
49
+ int_dtype=int_dtype,
50
+ overflow_safe_dtype=overflow_safe_dtype,
44
51
  )
45
52
  self.get_fft_plan = get_fft_plan
46
53
  self.affine_transform = affine_transform
47
54
  self.maximum_filter = maximum_filter
48
55
 
49
- floating = f"float{self.datatype_bytes(default_dtype) * 8}"
50
- integer = f"int{self.datatype_bytes(default_dtype_int) * 8}"
56
+ itype = f"int{self.datatype_bytes(int_dtype) * 8}"
57
+ ftype = f"float{self.datatype_bytes(float_dtype) * 8}"
51
58
  self._max_score_over_rotations = self._array_backend.ElementwiseKernel(
52
- f"{floating} internal_scores, {floating} scores, {integer} rot_index",
53
- f"{floating} out1, {integer} rotations",
59
+ f"{ftype} internal_scores, {ftype} scores, {itype} rot_index",
60
+ f"{ftype} out1, {itype} rotations",
54
61
  "if (internal_scores < scores) {out1 = scores; rotations = rot_index;}",
55
62
  "max_score_over_rotations",
56
63
  )
64
+ self.norm_scores = cp.ElementwiseKernel(
65
+ f"{ftype} arr, {ftype} exp_sq, {ftype} sq_exp, {ftype} n_obs, {ftype} eps",
66
+ f"{ftype} out",
67
+ """
68
+ // tmp1 = E(X)^2; tmp2 = E(X^2)
69
+ float tmp1 = sq_exp / n_obs;
70
+ float tmp2 = exp_sq / n_obs;
71
+ tmp1 *= tmp1;
72
+
73
+ tmp2 = sqrt(max(tmp2 - tmp1, 0.0));
74
+ // out = (tmp2 < eps) ? 0.0 : arr / (tmp2 * n_obs);
75
+ tmp1 = arr;
76
+ if (tmp2 < eps){
77
+ tmp1 = 0;
78
+ }
79
+ tmp2 *= n_obs;
80
+ out = tmp1 / tmp2;
81
+ """,
82
+ "norm_scores",
83
+ )
84
+ self.texture_available = find_spec("voltools") is not None
57
85
 
58
86
  def to_backend_array(self, arr: NDArray) -> CupyArray:
59
87
  current_device = self._array_backend.cuda.device.get_device_id()
@@ -70,35 +98,15 @@ class CupyBackend(NumpyFFTWBackend):
70
98
  def to_cpu_array(self, arr: NDArray) -> NDArray:
71
99
  return self.to_numpy_array(arr)
72
100
 
73
- def sharedarr_to_arr(
74
- self, shm: CupyArray, shape: Tuple[int], dtype: str
75
- ) -> CupyArray:
76
- return shm
101
+ def from_sharedarr(self, arr: CupyArray) -> CupyArray:
102
+ return arr
77
103
 
78
104
  @staticmethod
79
- def arr_to_sharedarr(
80
- arr: CupyArray, shared_memory_handler: type = None
81
- ) -> CupyArray:
105
+ def to_sharedarr(arr: CupyArray, shared_memory_handler: type = None) -> shm_type:
82
106
  return arr
83
107
 
84
- def preallocate_array(self, shape: Tuple[int], dtype: type) -> NDArray:
85
- """
86
- Returns a byte-aligned array of zeros with specified shape and dtype.
87
-
88
- Parameters
89
- ----------
90
- shape : Tuple[int]
91
- Desired shape for the array.
92
- dtype : type
93
- Desired data type for the array.
94
-
95
- Returns
96
- -------
97
- NDArray
98
- Byte-aligned array of zeros with specified shape and dtype.
99
- """
100
- arr = self._array_backend.zeros(shape, dtype=dtype)
101
- return arr
108
+ def zeros(self, *args, **kwargs):
109
+ return self._array_backend.zeros(*args, **kwargs)
102
110
 
103
111
  def unravel_index(self, indices, shape):
104
112
  return self._array_backend.unravel_index(indices=indices, dims=shape)
@@ -106,10 +114,10 @@ class CupyBackend(NumpyFFTWBackend):
106
114
  def unique(self, ar, axis=None, *args, **kwargs):
107
115
  if axis is None:
108
116
  return self._array_backend.unique(ar=ar, axis=axis, *args, **kwargs)
109
- warnings.warn("Axis argument not yet supported in CupY, falling back to NumPy.")
110
117
 
118
+ warnings.warn("Axis argument not yet supported in CupY, falling back to NumPy.")
111
119
  ret = np.unique(ar=self.to_numpy_array(ar), axis=axis, *args, **kwargs)
112
- if type(ret) != tuple:
120
+ if not isinstance(ret, tuple):
113
121
  return self.to_backend_array(ret)
114
122
  return tuple(self.to_backend_array(k) for k in ret)
115
123
 
@@ -119,40 +127,12 @@ class CupyBackend(NumpyFFTWBackend):
119
127
  fast_ft_shape: Tuple[int],
120
128
  real_dtype: type,
121
129
  complex_dtype: type,
122
- fftargs: Dict = {},
123
130
  inverse_fast_shape: Tuple[int] = None,
124
131
  **kwargs,
125
132
  ) -> Tuple[Callable, Callable]:
126
- """
127
- Build pyFFTW builder functions.
128
-
129
- Parameters
130
- ----------
131
- fast_shape : tuple
132
- Tuple of integers corresponding to fast convolution shape
133
- (see :py:meth:`CupyBackend.compute_convolution_shapes`).
134
- fast_ft_shape : tuple
135
- Tuple of integers corresponding to the shape of the fourier
136
- transform array (see :py:meth:`CupyBackend.compute_convolution_shapes`).
137
- real_dtype : dtype
138
- Numpy dtype of the inverse fourier transform.
139
- complex_dtype : dtype
140
- Numpy dtype of the fourier transform.
141
- inverse_fast_shape : tuple, optional
142
- Output shape of the inverse Fourier transform. By default fast_shape.
143
- fftargs : dict, optional
144
- Dictionary passed to pyFFTW builders.
145
- **kwargs: dict, optional
146
- Unused keyword arguments.
147
-
148
- Returns
149
- -------
150
- tuple
151
- Tupple containing callable rfft and irfft object.
152
- """
153
- cache = self._array_backend.fft.config.get_plan_cache()
154
- cache.set_size(2)
133
+ import cupyx.scipy.fft as cufft
155
134
 
135
+ cache = self._array_backend.fft.config.get_plan_cache()
156
136
  current_device = self._array_backend.cuda.device.get_device_id()
157
137
 
158
138
  previous_transform = [fast_shape, fast_ft_shape]
@@ -161,20 +141,18 @@ class CupyBackend(NumpyFFTWBackend):
161
141
 
162
142
  real_diff, cmplx_diff = True, True
163
143
  if len(fast_shape) == len(previous_transform[0]):
164
- real_diff = self.sum(self.subtract(fast_shape, previous_transform[0])) != 0
144
+ real_diff = fast_shape == previous_transform[0]
165
145
  if len(fast_ft_shape) == len(previous_transform[1]):
166
- cmplx_diff = (
167
- self.sum(self.subtract(fast_ft_shape, previous_transform[1])) != 0
168
- )
146
+ cmplx_diff = fast_ft_shape == previous_transform[1]
169
147
 
170
148
  if real_diff or cmplx_diff:
171
149
  cache.clear()
172
150
 
173
- def rfftn(arr: CupyArray, out: CupyArray) -> None:
174
- out[:] = self.fft.rfftn(arr)[:]
151
+ def rfftn(arr: CupyArray, out: CupyArray) -> CupyArray:
152
+ return cufft.rfftn(arr)
175
153
 
176
- def irfftn(arr: CupyArray, out: CupyArray) -> None:
177
- out[:] = self.fft.irfftn(arr)[:]
154
+ def irfftn(arr: CupyArray, out: CupyArray) -> CupyArray:
155
+ return cufft.irfftn(arr)
178
156
 
179
157
  PLAN_CACHE[current_device] = [fast_shape, fast_ft_shape]
180
158
 
@@ -182,11 +160,14 @@ class CupyBackend(NumpyFFTWBackend):
182
160
 
183
161
  def compute_convolution_shapes(
184
162
  self, arr1_shape: Tuple[int], arr2_shape: Tuple[int]
185
- ) -> Tuple[Tuple[int], Tuple[int], Tuple[int]]:
186
- ret = super().compute_convolution_shapes(arr1_shape, arr2_shape)
187
- convolution_shape, fast_shape, fast_ft_shape = ret
163
+ ) -> Tuple[List[int], List[int], List[int]]:
164
+ from cupyx.scipy.fft import next_fast_len
165
+
166
+ convolution_shape = [int(x + y - 1) for x, y in zip(arr1_shape, arr2_shape)]
167
+ fast_shape = [next_fast_len(x, real=True) for x in convolution_shape]
168
+ fast_ft_shape = list(fast_shape[:-1]) + [fast_shape[-1] // 2 + 1]
188
169
 
189
- # cuFFT plans do not support automatic padding yet.
170
+ # This almost never happens but avoid cuFFT casting errors
190
171
  is_odd = fast_shape[-1] % 2
191
172
  fast_shape[-1] += is_odd
192
173
  fast_ft_shape[-1] += is_odd
@@ -212,167 +193,81 @@ class CupyBackend(NumpyFFTWBackend):
212
193
  out = self.var(a, *args, **kwargs)
213
194
  return self._array_backend.sqrt(out)
214
195
 
215
- def rotate_array(
196
+ def _get_texture(self, arr: CupyArray, order: int = 3, prefilter: bool = False):
197
+ key = id(arr)
198
+ if key in TEXTURE_CACHE:
199
+ return TEXTURE_CACHE[key]
200
+
201
+ from voltools import StaticVolume
202
+
203
+ # Only keep template and potential corresponding mask in cache
204
+ if len(TEXTURE_CACHE) >= 2:
205
+ TEXTURE_CACHE.clear()
206
+
207
+ interpolation = "filt_bspline"
208
+ if order == 1:
209
+ interpolation = "linear"
210
+ elif order == 3 and not prefilter:
211
+ interpolation = "bspline"
212
+
213
+ current_device = self._array_backend.cuda.device.get_device_id()
214
+ TEXTURE_CACHE[key] = StaticVolume(
215
+ arr, interpolation=interpolation, device=f"gpu:{current_device}"
216
+ )
217
+
218
+ return TEXTURE_CACHE[key]
219
+
220
+ def _rigid_transform(
216
221
  self,
217
- arr: CupyArray,
218
- rotation_matrix: CupyArray,
219
- arr_mask: CupyArray = None,
220
- translation: CupyArray = None,
221
- use_geometric_center: bool = False,
222
- out: CupyArray = None,
223
- out_mask: CupyArray = None,
224
- order: int = 3,
222
+ data: CupyArray,
223
+ matrix: CupyArray,
224
+ output: CupyArray,
225
+ prefilter: bool,
226
+ order: int,
227
+ cache: bool = False,
225
228
  ) -> None:
226
- """
227
- Rotates coordinates of arr according to rotation_matrix.
228
-
229
- If no output array is provided, this method will compute an array with
230
- sufficient space to hold all elements. If both `arr` and `arr_mask`
231
- are provided, `arr_mask` will be centered according to arr.
232
-
233
- Parameters
234
- ----------
235
- arr : CupyArray
236
- The input array to be rotated.
237
- arr_mask : CupyArray, optional
238
- The mask of `arr` that will be equivalently rotated.
239
- rotation_matrix : CupyArray
240
- The rotation matrix to apply [d x d].
241
- translation : CupyArray
242
- The translation to apply [d].
243
- use_geometric_center : bool, optional
244
- Whether the rotation should be centered around the geometric
245
- or mass center. Default is mass center.
246
- out : CupyArray, optional
247
- The output array to write the rotation of `arr` to.
248
- out_mask : CupyArray, optional
249
- The output array to write the rotation of `arr_mask` to.
250
- order : int, optional
251
- Spline interpolation order. Has to be in the range 0-5.
252
-
253
- Notes
254
- -----
255
- Only a box of size arr, arr_mask will be consisdered for interpolation
256
- in out, out_mask.
257
- """
258
-
259
- rotate_mask = arr_mask is not None
260
- return_type = (out is None) + 2 * rotate_mask * (out_mask is None)
261
- translation = self.zeros(arr.ndim) if translation is None else translation
262
-
263
- center = self.divide(self.to_backend_array(arr.shape), 2)
264
- if not use_geometric_center:
265
- center = self.center_of_mass(arr, cutoff=0)
266
-
267
- rotation_matrix_inverted = self.linalg.inv(rotation_matrix)
268
- transformed_center = rotation_matrix_inverted @ center.reshape(-1, 1)
269
- transformed_center = transformed_center.reshape(-1)
270
- base_offset = self.subtract(center, transformed_center)
271
- offset = self.subtract(base_offset, translation)
272
-
273
- out = self.zeros_like(arr) if out is None else out
274
- out_slice = tuple(slice(0, stop) for stop in arr.shape)
275
-
276
- # Applying the prefilter leads to the creation of artifacts in the mask.
229
+ out_slice = tuple(slice(0, stop) for stop in data.shape)
230
+ if data.ndim == 3 and cache and self.texture_available:
231
+ # Device memory pool (should) come to rescue performance
232
+ temp = self.empty(data.shape, data.dtype)
233
+ texture = self._get_texture(data, order=order, prefilter=prefilter)
234
+ texture.affine(transform_m=matrix, profile=False, output=temp)
235
+ output[out_slice] = temp
236
+ return None
237
+
277
238
  self.affine_transform(
278
- input=arr,
279
- matrix=rotation_matrix_inverted,
280
- offset=offset,
239
+ input=data,
240
+ matrix=matrix,
281
241
  mode="constant",
282
- output=out[out_slice],
242
+ output=output[out_slice],
283
243
  order=order,
284
- prefilter=True,
244
+ prefilter=prefilter,
285
245
  )
286
246
 
287
- if rotate_mask:
288
- out_mask = self.zeros_like(arr_mask) if out_mask is None else out_mask
289
- out_mask_slice = tuple(slice(0, stop) for stop in arr_mask.shape)
290
- self.affine_transform(
291
- input=arr_mask,
292
- matrix=rotation_matrix_inverted,
293
- offset=offset,
294
- mode="constant",
295
- output=out_mask[out_mask_slice],
296
- order=order,
297
- prefilter=False,
298
- )
299
-
300
- match return_type:
301
- case 0:
302
- return None
303
- case 1:
304
- return out
305
- case 2:
306
- return out_mask
307
- case 3:
308
- return out, out_mask
309
-
310
247
  def get_available_memory(self) -> int:
311
248
  with self._array_backend.cuda.Device():
312
- (
313
- free_memory,
314
- available_memory,
315
- ) = self._array_backend.cuda.runtime.memGetInfo()
249
+ free_memory, _ = self._array_backend.cuda.runtime.memGetInfo()
316
250
  return free_memory
317
251
 
318
252
  @contextmanager
319
253
  def set_device(self, device_index: int):
320
- """
321
- Set the active GPU device as a context.
322
-
323
- This method sets the active GPU device for operations within the context.
324
-
325
- Parameters
326
- ----------
327
- device_index : int
328
- Index of the GPU device to be set as active.
329
-
330
- Yields
331
- ------
332
- None
333
- Operates as a context manager, yielding None and providing
334
- the set GPU context for enclosed operations.
335
- """
336
254
  with self._array_backend.cuda.Device(device_index):
337
255
  yield
338
256
 
339
257
  def device_count(self) -> int:
340
- """
341
- Return the number of available GPU devices.
342
-
343
- Returns
344
- -------
345
- int
346
- Number of available GPU devices.
347
- """
348
258
  return self._array_backend.cuda.runtime.getDeviceCount()
349
259
 
350
260
  def max_score_over_rotations(
351
261
  self,
352
- score_space: CupyArray,
353
- internal_scores: CupyArray,
354
- internal_rotations: CupyArray,
262
+ scores: CupyArray,
263
+ max_scores: CupyArray,
264
+ rotations: CupyArray,
355
265
  rotation_index: int,
356
- ):
357
- """
358
- Modify internal_scores and internal_rotations inplace with scores and rotation
359
- index respectively, wherever score_sapce is larger than internal scores.
360
-
361
- Parameters
362
- ----------
363
- score_space : CupyArray
364
- The score space to compare against internal_scores.
365
- internal_scores : CupyArray
366
- The internal scores to update with maximum scores.
367
- internal_rotations : CupyArray
368
- The internal rotations corresponding to the maximum scores.
369
- rotation_index : int
370
- The index representing the current rotation.
371
- """
372
- self._max_score_over_rotations(
373
- internal_scores,
374
- score_space,
266
+ ) -> Tuple[CupyArray, CupyArray]:
267
+ return self._max_score_over_rotations(
268
+ max_scores,
269
+ scores,
375
270
  rotation_index,
376
- internal_scores,
377
- internal_rotations,
271
+ max_scores,
272
+ rotations,
378
273
  )