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