pytme 0.2.9__cp311-cp311-macosx_15_0_arm64.whl → 0.3.0__cp311-cp311-macosx_15_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 (75) hide show
  1. pytme-0.3.0.data/scripts/estimate_memory_usage.py +76 -0
  2. pytme-0.3.0.data/scripts/match_template.py +1106 -0
  3. {pytme-0.2.9.data → pytme-0.3.0.data}/scripts/postprocess.py +320 -190
  4. {pytme-0.2.9.data → pytme-0.3.0.data}/scripts/preprocess.py +21 -31
  5. {pytme-0.2.9.data → pytme-0.3.0.data}/scripts/preprocessor_gui.py +85 -19
  6. pytme-0.3.0.data/scripts/pytme_runner.py +771 -0
  7. {pytme-0.2.9.dist-info → pytme-0.3.0.dist-info}/METADATA +22 -20
  8. pytme-0.3.0.dist-info/RECORD +126 -0
  9. {pytme-0.2.9.dist-info → pytme-0.3.0.dist-info}/entry_points.txt +2 -1
  10. pytme-0.3.0.dist-info/licenses/LICENSE +339 -0
  11. scripts/estimate_memory_usage.py +76 -0
  12. scripts/eval.py +93 -0
  13. scripts/extract_candidates.py +224 -0
  14. scripts/match_template.py +349 -378
  15. pytme-0.2.9.data/scripts/match_template.py → scripts/match_template_filters.py +213 -148
  16. scripts/postprocess.py +320 -190
  17. scripts/preprocess.py +21 -31
  18. scripts/preprocessor_gui.py +85 -19
  19. scripts/pytme_runner.py +771 -0
  20. scripts/refine_matches.py +625 -0
  21. tests/preprocessing/test_frequency_filters.py +28 -14
  22. tests/test_analyzer.py +41 -36
  23. tests/test_backends.py +1 -0
  24. tests/test_matching_cli.py +109 -53
  25. tests/test_matching_data.py +5 -5
  26. tests/test_matching_exhaustive.py +1 -2
  27. tests/test_matching_optimization.py +4 -9
  28. tests/test_matching_utils.py +1 -1
  29. tests/test_orientations.py +0 -1
  30. tme/__version__.py +1 -1
  31. tme/analyzer/__init__.py +2 -0
  32. tme/analyzer/_utils.py +26 -21
  33. tme/analyzer/aggregation.py +396 -222
  34. tme/analyzer/base.py +127 -0
  35. tme/analyzer/peaks.py +189 -201
  36. tme/analyzer/proxy.py +123 -0
  37. tme/backends/__init__.py +4 -3
  38. tme/backends/_cupy_utils.py +25 -24
  39. tme/backends/_jax_utils.py +20 -18
  40. tme/backends/cupy_backend.py +13 -26
  41. tme/backends/jax_backend.py +24 -23
  42. tme/backends/matching_backend.py +4 -3
  43. tme/backends/mlx_backend.py +4 -3
  44. tme/backends/npfftw_backend.py +34 -30
  45. tme/backends/pytorch_backend.py +18 -4
  46. tme/cli.py +126 -0
  47. tme/density.py +9 -7
  48. tme/extensions.cpython-311-darwin.so +0 -0
  49. tme/filters/__init__.py +3 -3
  50. tme/filters/_utils.py +36 -10
  51. tme/filters/bandpass.py +229 -188
  52. tme/filters/compose.py +5 -4
  53. tme/filters/ctf.py +516 -254
  54. tme/filters/reconstruction.py +91 -32
  55. tme/filters/wedge.py +196 -135
  56. tme/filters/whitening.py +37 -42
  57. tme/matching_data.py +28 -39
  58. tme/matching_exhaustive.py +31 -27
  59. tme/matching_optimization.py +5 -4
  60. tme/matching_scores.py +25 -15
  61. tme/matching_utils.py +158 -28
  62. tme/memory.py +4 -3
  63. tme/orientations.py +22 -9
  64. tme/parser.py +114 -33
  65. tme/preprocessor.py +6 -5
  66. tme/rotations.py +10 -7
  67. tme/structure.py +4 -3
  68. pytme-0.2.9.data/scripts/estimate_ram_usage.py +0 -97
  69. pytme-0.2.9.dist-info/RECORD +0 -119
  70. pytme-0.2.9.dist-info/licenses/LICENSE +0 -153
  71. scripts/estimate_ram_usage.py +0 -97
  72. tests/data/Maps/.DS_Store +0 -0
  73. tests/data/Structures/.DS_Store +0 -0
  74. {pytme-0.2.9.dist-info → pytme-0.3.0.dist-info}/WHEEL +0 -0
  75. {pytme-0.2.9.dist-info → pytme-0.3.0.dist-info}/top_level.txt +0 -0
tme/filters/bandpass.py CHANGED
@@ -1,230 +1,271 @@
1
- """ Implements class BandPassFilter to create Fourier filter representations.
1
+ """
2
+ Implements class BandPassFilter to create Fourier filter representations.
2
3
 
3
- Copyright (c) 2024 European Molecular Biology Laboratory
4
+ Copyright (c) 2024 European Molecular Biology Laboratory
4
5
 
5
- Author: Valentin Maurer <valentin.maurer@embl-hamburg.de>
6
+ Author: Valentin Maurer <valentin.maurer@embl-hamburg.de>
6
7
  """
7
8
 
8
9
  from typing import Tuple
9
10
  from math import log, sqrt
11
+ from dataclasses import dataclass
12
+
13
+ import numpy as np
10
14
 
11
15
  from ..types import BackendArray
12
16
  from ..backends import backend as be
13
17
  from .compose import ComposableFilter
14
- from ._utils import fftfreqn, crop_real_fourier, shift_fourier
18
+ from ._utils import (
19
+ crop_real_fourier,
20
+ shift_fourier,
21
+ pad_to_length,
22
+ frequency_grid_at_angle,
23
+ fftfreqn,
24
+ )
15
25
 
16
- __all__ = ["BandPassFilter"]
26
+ __all__ = ["BandPass", "BandPassReconstructed"]
17
27
 
18
28
 
19
- class BandPassFilter(ComposableFilter):
29
+ @dataclass
30
+ class BandPass(ComposableFilter):
20
31
  """
21
- Generate bandpass filters in Fourier space.
22
-
23
- Parameters
24
- ----------
25
- lowpass : float, optional
26
- The lowpass cutoff, defaults to None.
27
- highpass : float, optional
28
- The highpass cutoff, defaults to None.
29
- sampling_rate : Tuple[float], optional
30
- The sampling r_position_to_molmapate in Fourier space, defaults to 1.
31
- use_gaussian : bool, optional
32
- Whether to use Gaussian bandpass filter, defaults to True.
33
- return_real_fourier : bool, optional
34
- Whether to return only the real Fourier space, defaults to False.
35
- shape_is_real_fourier : bool, optional
36
- Whether the shape represents the real Fourier space, defaults to False.
32
+ Generate per-slice Fourier Bandpass filter
37
33
  """
38
34
 
39
- def __init__(
40
- self,
41
- lowpass: float = None,
42
- highpass: float = None,
43
- sampling_rate: Tuple[float] = 1,
44
- use_gaussian: bool = True,
45
- return_real_fourier: bool = False,
46
- shape_is_real_fourier: bool = False,
47
- ):
48
- self.lowpass = lowpass
49
- self.highpass = highpass
50
- self.use_gaussian = use_gaussian
51
- self.return_real_fourier = return_real_fourier
52
- self.shape_is_real_fourier = shape_is_real_fourier
53
- self.sampling_rate = sampling_rate
54
-
55
- @staticmethod
56
- def discrete_bandpass(
57
- shape: Tuple[int],
58
- lowpass: float,
59
- highpass: float,
60
- sampling_rate: Tuple[float],
61
- return_real_fourier: bool = False,
62
- shape_is_real_fourier: bool = False,
63
- **kwargs,
64
- ) -> BackendArray:
35
+ #: The tilt angles.
36
+ angles: Tuple[float]
37
+ #: The lowpass cutoffs. Either one or one per angle, defaults to None.
38
+ lowpass: Tuple[float] = None
39
+ #: The highpass cutoffs. Either one or one per angle, defaults to None.
40
+ highpass: Tuple[float] = None
41
+ #: The shape of the to-be created mask.
42
+ shape: Tuple[int] = None
43
+ #: Axis the plane is tilted over, defaults to 0 (x).
44
+ tilt_axis: int = 0
45
+ #: The projection axis, defaults to 2 (z).
46
+ opening_axis: int = 2
47
+ #: The sampling rate, defaults to 1 Ångstrom / voxel.
48
+ sampling_rate: Tuple[float] = 1
49
+ #: Whether to use Gaussian bandpass filter, defaults to True.
50
+ use_gaussian: bool = True
51
+ #: Whether to return a mask for rfft
52
+ return_real_fourier: bool = False
53
+
54
+ def __call__(self, **kwargs):
65
55
  """
66
- Generate a bandpass filter using discrete frequency cutoffs.
67
-
68
- Parameters
69
- ----------
70
- shape : tuple of int
71
- The shape of the bandpass filter.
72
- lowpass : float
73
- The lowpass cutoff in units of sampling rate.
74
- highpass : float
75
- The highpass cutoff in units of sampling rate.
76
- return_real_fourier : bool, optional
77
- Whether to return only the real Fourier space, defaults to False.
78
- sampling_rate : float
79
- The sampling rate in Fourier space.
80
- shape_is_real_fourier : bool, optional
81
- Whether the shape represents the real Fourier space, defaults to False.
82
- **kwargs : dict
83
- Additional keyword arguments.
84
-
85
- Returns
86
- -------
87
- BackendArray
88
- The bandpass filter in Fourier space.
56
+ Returns a Bandpass stack of chosen parameters with DC component in the center.
89
57
  """
58
+ func_args = vars(self).copy()
59
+ func_args.update(kwargs)
60
+
61
+ func = discrete_bandpass
62
+ if func_args.get("use_gaussian"):
63
+ func = gaussian_bandpass
64
+
65
+ return_real_fourier = kwargs.get("return_real_fourier", True)
66
+ shape_is_real_fourier = kwargs.get("shape_is_real_fourier", False)
90
67
  if shape_is_real_fourier:
91
68
  return_real_fourier = False
92
69
 
93
- grid = fftfreqn(
94
- shape=shape,
95
- sampling_rate=0.5,
96
- shape_is_real_fourier=shape_is_real_fourier,
97
- compute_euclidean_norm=True,
98
- )
99
- grid = be.astype(be.to_backend_array(grid), be._float_dtype)
100
- sampling_rate = be.to_backend_array(sampling_rate)
70
+ angles = np.atleast_1d(func_args["angles"])
71
+ _lowpass = pad_to_length(func_args["lowpass"], angles.size)
72
+ _highpass = pad_to_length(func_args["highpass"], angles.size)
73
+
74
+ masks = []
75
+ for index, angle in enumerate(angles):
76
+ frequency_grid = frequency_grid_at_angle(
77
+ shape=func_args["shape"],
78
+ tilt_axis=func_args["tilt_axis"],
79
+ opening_axis=func_args["opening_axis"],
80
+ angle=angle,
81
+ sampling_rate=1,
82
+ )
83
+ func_args["lowpass"] = _lowpass[index]
84
+ func_args["highpass"] = _highpass[index]
85
+ mask = func(grid=frequency_grid, **func_args)
101
86
 
102
- highcut = grid.max()
103
- if lowpass is not None:
104
- highcut = be.max(2 * sampling_rate / lowpass)
87
+ mask = shift_fourier(data=mask, shape_is_real_fourier=shape_is_real_fourier)
88
+ if return_real_fourier:
89
+ mask = crop_real_fourier(mask)
90
+ masks.append(mask[None])
105
91
 
106
- lowcut = 0
107
- if highpass is not None:
108
- lowcut = be.max(2 * sampling_rate / highpass)
92
+ masks = be.concatenate(masks, axis=0)
93
+ return {
94
+ "data": be.to_backend_array(masks),
95
+ "shape": func_args["shape"],
96
+ "return_real_fourier": return_real_fourier,
97
+ "is_multiplicative_filter": True,
98
+ }
109
99
 
110
- bandpass_filter = ((grid <= highcut) & (grid >= lowcut)) * 1.0
111
- bandpass_filter = shift_fourier(
112
- data=bandpass_filter, shape_is_real_fourier=shape_is_real_fourier
113
- )
114
100
 
115
- if return_real_fourier:
116
- bandpass_filter = crop_real_fourier(bandpass_filter)
117
-
118
- return bandpass_filter
119
-
120
- @staticmethod
121
- def gaussian_bandpass(
122
- shape: Tuple[int],
123
- lowpass: float,
124
- highpass: float,
125
- sampling_rate: float,
126
- return_real_fourier: bool = False,
127
- shape_is_real_fourier: bool = False,
128
- **kwargs,
129
- ) -> BackendArray:
130
- """
131
- Generate a bandpass filter using Gaussians.
132
-
133
- Parameters
134
- ----------
135
- shape : tuple of int
136
- The shape of the bandpass filter.
137
- lowpass : float
138
- The lowpass cutoff in units of sampling rate.
139
- highpass : float
140
- The highpass cutoff in units of sampling rate.
141
- sampling_rate : float
142
- The sampling rate in Fourier space.
143
- return_real_fourier : bool, optional
144
- Whether to return only the real Fourier space, defaults to False.
145
- shape_is_real_fourier : bool, optional
146
- Whether the shape represents the real Fourier space, defaults to False.
147
- **kwargs : dict
148
- Additional keyword arguments.
149
-
150
- Returns
151
- -------
152
- BackendArray
153
- The bandpass filter in Fourier space.
154
- """
101
+ @dataclass
102
+ class BandPassReconstructed(ComposableFilter):
103
+ """
104
+ Generate reconstructed bandpass filters in Fourier space.
105
+ """
106
+
107
+ #: The lowpass cutoff, defaults to None.
108
+ lowpass: float = None
109
+ #: The highpass cutoff, defaults to None.
110
+ highpass: float = None
111
+ #: The shape of the to-be created mask.
112
+ shape: Tuple[int] = None
113
+ #: Axis the plane is tilted over, defaults to 0 (x).
114
+ tilt_axis: int = 0
115
+ #: The projection axis, defaults to 2 (z).
116
+ opening_axis: int = 2
117
+ #: The sampling rate, defaults to 1 Ångstrom / voxel.
118
+ sampling_rate: Tuple[float] = 1
119
+ #: Whether to use Gaussian bandpass filter, defaults to True.
120
+ use_gaussian: bool = True
121
+ #: Whether to return a mask for rfft
122
+ return_real_fourier: bool = False
123
+
124
+ def __call__(self, **kwargs):
125
+ func_args = vars(self).copy()
126
+ func_args.update(kwargs)
127
+
128
+ func = discrete_bandpass
129
+ if func_args.get("use_gaussian"):
130
+ func = gaussian_bandpass
131
+
132
+ return_real_fourier = func_args.get("return_real_fourier", True)
133
+ shape_is_real_fourier = func_args.get("shape_is_real_fourier", False)
155
134
  if shape_is_real_fourier:
156
135
  return_real_fourier = False
157
136
 
158
137
  grid = fftfreqn(
159
- shape=shape,
138
+ shape=func_args["shape"],
160
139
  sampling_rate=0.5,
161
140
  shape_is_real_fourier=shape_is_real_fourier,
162
141
  compute_euclidean_norm=True,
163
142
  )
164
- grid = be.astype(be.to_backend_array(grid), be._float_dtype)
165
- grid = -be.square(grid, out=grid)
143
+ mask = func(grid=grid, **func_args)
166
144
 
167
- has_lowpass, has_highpass = False, False
168
- norm = float(sqrt(2 * log(2)))
169
- upper_sampling = float(
170
- be.max(be.multiply(2, be.to_backend_array(sampling_rate)))
171
- )
145
+ mask = shift_fourier(data=mask, shape_is_real_fourier=shape_is_real_fourier)
146
+ if return_real_fourier:
147
+ mask = crop_real_fourier(mask)
172
148
 
173
- if lowpass is not None:
174
- lowpass, has_lowpass = float(lowpass), True
175
- lowpass = be.maximum(lowpass, be.eps(be._float_dtype))
176
- if highpass is not None:
177
- highpass, has_highpass = float(highpass), True
178
- highpass = be.maximum(highpass, be.eps(be._float_dtype))
179
-
180
- if has_lowpass:
181
- lowpass = upper_sampling / (lowpass * norm)
182
- lowpass = be.multiply(2, be.square(lowpass))
183
- if not has_highpass:
184
- lowpass_filter = be.divide(grid, lowpass, out=grid)
185
- else:
186
- lowpass_filter = be.divide(grid, lowpass)
187
- lowpass_filter = be.exp(lowpass_filter, out=lowpass_filter)
188
-
189
- if has_highpass:
190
- highpass = upper_sampling / (highpass * norm)
191
- highpass = be.multiply(2, be.square(highpass))
192
- highpass_filter = be.divide(grid, highpass, out=grid)
193
- highpass_filter = be.exp(highpass_filter, out=highpass_filter)
194
- highpass_filter = be.subtract(1, highpass_filter, out=highpass_filter)
195
-
196
- if has_lowpass and not has_highpass:
197
- bandpass_filter = lowpass_filter
198
- elif not has_lowpass and has_highpass:
199
- bandpass_filter = highpass_filter
200
- elif has_lowpass and has_highpass:
201
- bandpass_filter = be.multiply(
202
- lowpass_filter, highpass_filter, out=lowpass_filter
203
- )
204
- else:
205
- bandpass_filter = be.full(shape, fill_value=1, dtype=be._float_dtype)
149
+ return {
150
+ "data": be.to_backend_array(mask),
151
+ "shape": func_args["shape"],
152
+ "return_real_fourier": return_real_fourier,
153
+ "is_multiplicative_filter": True,
154
+ }
206
155
 
207
- bandpass_filter = shift_fourier(
208
- data=bandpass_filter, shape_is_real_fourier=shape_is_real_fourier
209
- )
210
156
 
211
- if return_real_fourier:
212
- bandpass_filter = crop_real_fourier(bandpass_filter)
157
+ def discrete_bandpass(
158
+ grid: BackendArray,
159
+ lowpass: float,
160
+ highpass: float,
161
+ sampling_rate: Tuple[float],
162
+ **kwargs,
163
+ ) -> BackendArray:
164
+ """
165
+ Generate a bandpass filter using discrete frequency cutoffs.
213
166
 
214
- return bandpass_filter
167
+ Parameters
168
+ ----------
169
+ grid : BackendArray
170
+ Frequencies in Fourier space.
171
+ lowpass : float
172
+ The lowpass cutoff in units of sampling rate.
173
+ highpass : float
174
+ The highpass cutoff in units of sampling rate.
175
+ return_real_fourier : bool, optional
176
+ Whether to return only the real Fourier space, defaults to False.
177
+ sampling_rate : float
178
+ The sampling rate in Fourier space.
179
+ **kwargs : dict
180
+ Additional keyword arguments.
181
+
182
+ Returns
183
+ -------
184
+ BackendArray
185
+ The bandpass filter in Fourier space.
186
+ """
187
+ grid = be.astype(be.to_backend_array(grid), be._float_dtype)
188
+ sampling_rate = be.to_backend_array(sampling_rate)
215
189
 
216
- def __call__(self, **kwargs):
217
- func_args = vars(self)
218
- func_args.update(kwargs)
190
+ highcut = grid.max()
191
+ if lowpass is not None:
192
+ highcut = be.max(2 * sampling_rate / lowpass)
219
193
 
220
- func = self.discrete_bandpass
221
- if func_args.get("use_gaussian"):
222
- func = self.gaussian_bandpass
194
+ lowcut = 0
195
+ if highpass is not None:
196
+ lowcut = be.max(2 * sampling_rate / highpass)
223
197
 
224
- mask = func(**func_args)
198
+ bandpass_filter = ((grid <= highcut) & (grid >= lowcut)) * 1.0
199
+ return bandpass_filter
225
200
 
226
- return {
227
- "data": be.to_backend_array(mask),
228
- "sampling_rate": func_args.get("sampling_rate", 1),
229
- "is_multiplicative_filter": True,
230
- }
201
+
202
+ def gaussian_bandpass(
203
+ grid: BackendArray,
204
+ lowpass: float = None,
205
+ highpass: float = None,
206
+ sampling_rate: float = 1,
207
+ **kwargs,
208
+ ) -> BackendArray:
209
+ """
210
+ Generate a bandpass filter using Gaussians.
211
+
212
+ Parameters
213
+ ----------
214
+ grid : BackendArray
215
+ Frequency grid in Fourier space.
216
+ lowpass : float, optional
217
+ The lowpass cutoff in units of sampling rate, defaults to None.
218
+ highpass : float, optional
219
+ The highpass cutoff in units of sampling rate, defaults to None.
220
+ sampling_rate : float, optional
221
+ The sampling rate in Fourier space, defaults to one.
222
+ **kwargs : dict
223
+ Additional keyword arguments.
224
+
225
+ Returns
226
+ -------
227
+ BackendArray
228
+ The bandpass filter in Fourier space.
229
+ """
230
+ grid = be.astype(be.to_backend_array(grid), be._float_dtype)
231
+ grid = -be.square(grid, out=grid)
232
+
233
+ has_lowpass, has_highpass = False, False
234
+ norm = float(sqrt(2 * log(2)))
235
+ upper_sampling = float(be.max(be.multiply(2, be.to_backend_array(sampling_rate))))
236
+
237
+ if lowpass is not None:
238
+ lowpass, has_lowpass = float(lowpass), True
239
+ lowpass = be.maximum(lowpass, be.eps(be._float_dtype))
240
+ if highpass is not None:
241
+ highpass, has_highpass = float(highpass), True
242
+ highpass = be.maximum(highpass, be.eps(be._float_dtype))
243
+
244
+ if has_lowpass:
245
+ lowpass = upper_sampling / (lowpass * norm)
246
+ lowpass = be.multiply(2, be.square(lowpass))
247
+ if not has_highpass:
248
+ lowpass_filter = be.divide(grid, lowpass, out=grid)
249
+ else:
250
+ lowpass_filter = be.divide(grid, lowpass)
251
+ lowpass_filter = be.exp(lowpass_filter, out=lowpass_filter)
252
+
253
+ if has_highpass:
254
+ highpass = upper_sampling / (highpass * norm)
255
+ highpass = be.multiply(2, be.square(highpass))
256
+ highpass_filter = be.divide(grid, highpass, out=grid)
257
+ highpass_filter = be.exp(highpass_filter, out=highpass_filter)
258
+ highpass_filter = be.subtract(1, highpass_filter, out=highpass_filter)
259
+
260
+ if has_lowpass and not has_highpass:
261
+ bandpass_filter = lowpass_filter
262
+ elif not has_lowpass and has_highpass:
263
+ bandpass_filter = highpass_filter
264
+ elif has_lowpass and has_highpass:
265
+ bandpass_filter = be.multiply(
266
+ lowpass_filter, highpass_filter, out=lowpass_filter
267
+ )
268
+ else:
269
+ bandpass_filter = be.full(grid.shape, fill_value=1, dtype=be._float_dtype)
270
+
271
+ return bandpass_filter
tme/filters/compose.py CHANGED
@@ -1,8 +1,9 @@
1
- """ Combine filters using an interface analogous to pytorch's Compose.
1
+ """
2
+ Combine filters using an interface analogous to pytorch's Compose.
2
3
 
3
- Copyright (c) 2024 European Molecular Biology Laboratory
4
+ Copyright (c) 2024 European Molecular Biology Laboratory
4
5
 
5
- Author: Valentin Maurer <valentin.maurer@embl-hamburg.de>
6
+ Author: Valentin Maurer <valentin.maurer@embl-hamburg.de>
6
7
  """
7
8
 
8
9
  from typing import Tuple, Dict
@@ -60,7 +61,7 @@ class Compose:
60
61
 
61
62
  class ComposableFilter(ABC):
62
63
  """
63
- Strategy class for composable filters.
64
+ Base class for composable filters.
64
65
  """
65
66
 
66
67
  @abstractmethod