pytme 0.2.0b0__cp311-cp311-macosx_14_0_arm64.whl → 0.2.1__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.
- {pytme-0.2.0b0.data → pytme-0.2.1.data}/scripts/match_template.py +473 -140
- {pytme-0.2.0b0.data → pytme-0.2.1.data}/scripts/postprocess.py +107 -49
- {pytme-0.2.0b0.data → pytme-0.2.1.data}/scripts/preprocessor_gui.py +4 -1
- {pytme-0.2.0b0.dist-info → pytme-0.2.1.dist-info}/METADATA +2 -2
- pytme-0.2.1.dist-info/RECORD +73 -0
- scripts/extract_candidates.py +117 -85
- scripts/match_template.py +473 -140
- scripts/match_template_filters.py +458 -169
- scripts/postprocess.py +107 -49
- scripts/preprocessor_gui.py +4 -1
- scripts/refine_matches.py +364 -160
- tme/__version__.py +1 -1
- tme/analyzer.py +278 -148
- tme/backends/__init__.py +1 -0
- tme/backends/cupy_backend.py +20 -13
- tme/backends/jax_backend.py +218 -0
- tme/backends/matching_backend.py +25 -10
- tme/backends/mlx_backend.py +13 -9
- tme/backends/npfftw_backend.py +22 -12
- tme/backends/pytorch_backend.py +20 -9
- tme/density.py +85 -64
- tme/extensions.cpython-311-darwin.so +0 -0
- tme/matching_data.py +86 -60
- tme/matching_exhaustive.py +245 -166
- tme/matching_optimization.py +137 -69
- tme/matching_utils.py +1 -1
- tme/orientations.py +175 -55
- tme/preprocessing/__init__.py +2 -0
- tme/preprocessing/_utils.py +188 -0
- tme/preprocessing/composable_filter.py +31 -0
- tme/preprocessing/compose.py +51 -0
- tme/preprocessing/frequency_filters.py +378 -0
- tme/preprocessing/tilt_series.py +1017 -0
- tme/preprocessor.py +17 -7
- tme/structure.py +4 -1
- pytme-0.2.0b0.dist-info/RECORD +0 -66
- {pytme-0.2.0b0.data → pytme-0.2.1.data}/scripts/estimate_ram_usage.py +0 -0
- {pytme-0.2.0b0.data → pytme-0.2.1.data}/scripts/preprocess.py +0 -0
- {pytme-0.2.0b0.dist-info → pytme-0.2.1.dist-info}/LICENSE +0 -0
- {pytme-0.2.0b0.dist-info → pytme-0.2.1.dist-info}/WHEEL +0 -0
- {pytme-0.2.0b0.dist-info → pytme-0.2.1.dist-info}/entry_points.txt +0 -0
- {pytme-0.2.0b0.dist-info → pytme-0.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,378 @@
|
|
1
|
+
""" Defines Fourier frequency filters.
|
2
|
+
|
3
|
+
Copyright (c) 2024 European Molecular Biology Laboratory
|
4
|
+
|
5
|
+
Author: Valentin Maurer <valentin.maurer@embl-hamburg.de>
|
6
|
+
"""
|
7
|
+
from math import log, sqrt
|
8
|
+
from typing import Tuple, Dict
|
9
|
+
|
10
|
+
import numpy as np
|
11
|
+
from numpy.typing import NDArray
|
12
|
+
from scipy.ndimage import mean as ndimean
|
13
|
+
from scipy.ndimage import map_coordinates
|
14
|
+
|
15
|
+
from ._utils import fftfreqn, crop_real_fourier, shift_fourier
|
16
|
+
from ..backends import backend
|
17
|
+
|
18
|
+
|
19
|
+
class BandPassFilter:
|
20
|
+
"""
|
21
|
+
This class provides methods to generate bandpass filters in Fourier space,
|
22
|
+
either by directly specifying the frequency cutoffs (discrete_bandpass) or
|
23
|
+
by using Gaussian functions (gaussian_bandpass).
|
24
|
+
|
25
|
+
Parameters:
|
26
|
+
-----------
|
27
|
+
lowpass : float, optional
|
28
|
+
The lowpass cutoff, defaults to None.
|
29
|
+
highpass : float, optional
|
30
|
+
The highpass cutoff, defaults to None.
|
31
|
+
sampling_rate : Tuple[float], optional
|
32
|
+
The sampling rate in Fourier space, defaults to 1.
|
33
|
+
use_gaussian : bool, optional
|
34
|
+
Whether to use Gaussian bandpass filter, defaults to True.
|
35
|
+
return_real_fourier : bool, optional
|
36
|
+
Whether to return only the real Fourier space, defaults to False.
|
37
|
+
shape_is_real_fourier : bool, optional
|
38
|
+
Whether the shape represents the real Fourier space, defaults to False.
|
39
|
+
"""
|
40
|
+
|
41
|
+
def __init__(
|
42
|
+
self,
|
43
|
+
lowpass: float = None,
|
44
|
+
highpass: float = None,
|
45
|
+
sampling_rate: Tuple[float] = 1,
|
46
|
+
use_gaussian: bool = True,
|
47
|
+
return_real_fourier: bool = False,
|
48
|
+
shape_is_real_fourier: bool = False,
|
49
|
+
):
|
50
|
+
self.lowpass = lowpass
|
51
|
+
self.highpass = highpass
|
52
|
+
self.use_gaussian = use_gaussian
|
53
|
+
self.return_real_fourier = return_real_fourier
|
54
|
+
self.shape_is_real_fourier = shape_is_real_fourier
|
55
|
+
self.sampling_rate = sampling_rate
|
56
|
+
|
57
|
+
@staticmethod
|
58
|
+
def discrete_bandpass(
|
59
|
+
shape: Tuple[int],
|
60
|
+
lowpass: float,
|
61
|
+
highpass: float,
|
62
|
+
sampling_rate: Tuple[float],
|
63
|
+
return_real_fourier: bool = False,
|
64
|
+
shape_is_real_fourier: bool = False,
|
65
|
+
**kwargs,
|
66
|
+
) -> NDArray:
|
67
|
+
"""
|
68
|
+
Generate a bandpass filter using discrete frequency cutoffs.
|
69
|
+
|
70
|
+
Parameters:
|
71
|
+
-----------
|
72
|
+
shape : tuple of int
|
73
|
+
The shape of the bandpass filter.
|
74
|
+
lowpass : float
|
75
|
+
The lowpass cutoff in units of sampling rate.
|
76
|
+
highpass : float
|
77
|
+
The highpass cutoff in units of sampling rate.
|
78
|
+
return_real_fourier : bool, optional
|
79
|
+
Whether to return only the real Fourier space, defaults to False.
|
80
|
+
sampling_rate : float
|
81
|
+
The sampling rate in Fourier space.
|
82
|
+
shape_is_real_fourier : bool, optional
|
83
|
+
Whether the shape represents the real Fourier space, defaults to False.
|
84
|
+
**kwargs : dict
|
85
|
+
Additional keyword arguments.
|
86
|
+
|
87
|
+
Returns:
|
88
|
+
--------
|
89
|
+
NDArray
|
90
|
+
The bandpass filter in Fourier space.
|
91
|
+
"""
|
92
|
+
if shape_is_real_fourier:
|
93
|
+
return_real_fourier = False
|
94
|
+
|
95
|
+
grid = fftfreqn(
|
96
|
+
shape=shape,
|
97
|
+
sampling_rate=0.5,
|
98
|
+
shape_is_real_fourier=shape_is_real_fourier,
|
99
|
+
compute_euclidean_norm=True,
|
100
|
+
)
|
101
|
+
|
102
|
+
lowpass = 0 if lowpass is None else lowpass
|
103
|
+
highpass = 1e10 if highpass is None else highpass
|
104
|
+
|
105
|
+
highcut = grid.max()
|
106
|
+
if lowpass > 0:
|
107
|
+
highcut = np.max(2 * sampling_rate / lowpass)
|
108
|
+
lowcut = np.max(2 * sampling_rate / highpass)
|
109
|
+
|
110
|
+
bandpass_filter = ((grid <= highcut) & (grid >= lowcut)) * 1.0
|
111
|
+
|
112
|
+
bandpass_filter = shift_fourier(
|
113
|
+
data=bandpass_filter, shape_is_real_fourier=shape_is_real_fourier
|
114
|
+
)
|
115
|
+
|
116
|
+
if return_real_fourier:
|
117
|
+
bandpass_filter = crop_real_fourier(bandpass_filter)
|
118
|
+
|
119
|
+
return bandpass_filter
|
120
|
+
|
121
|
+
@staticmethod
|
122
|
+
def gaussian_bandpass(
|
123
|
+
shape: Tuple[int],
|
124
|
+
lowpass: float,
|
125
|
+
highpass: float,
|
126
|
+
sampling_rate: float,
|
127
|
+
return_real_fourier: bool = False,
|
128
|
+
shape_is_real_fourier: bool = False,
|
129
|
+
**kwargs,
|
130
|
+
) -> NDArray:
|
131
|
+
"""
|
132
|
+
Generate a bandpass filter using Gaussian functions.
|
133
|
+
|
134
|
+
Parameters:
|
135
|
+
-----------
|
136
|
+
shape : tuple of int
|
137
|
+
The shape of the bandpass filter.
|
138
|
+
lowpass : float
|
139
|
+
The lowpass cutoff in units of sampling rate.
|
140
|
+
highpass : float
|
141
|
+
The highpass cutoff in units of sampling rate.
|
142
|
+
sampling_rate : float
|
143
|
+
The sampling rate in Fourier space.
|
144
|
+
return_real_fourier : bool, optional
|
145
|
+
Whether to return only the real Fourier space, defaults to False.
|
146
|
+
shape_is_real_fourier : bool, optional
|
147
|
+
Whether the shape represents the real Fourier space, defaults to False.
|
148
|
+
**kwargs : dict
|
149
|
+
Additional keyword arguments.
|
150
|
+
|
151
|
+
Returns:
|
152
|
+
--------
|
153
|
+
NDArray
|
154
|
+
The bandpass filter in Fourier space.
|
155
|
+
"""
|
156
|
+
if shape_is_real_fourier:
|
157
|
+
return_real_fourier = False
|
158
|
+
|
159
|
+
grid = fftfreqn(
|
160
|
+
shape=shape,
|
161
|
+
sampling_rate=0.5,
|
162
|
+
shape_is_real_fourier=shape_is_real_fourier,
|
163
|
+
compute_euclidean_norm=True,
|
164
|
+
)
|
165
|
+
grid = -backend.square(grid)
|
166
|
+
|
167
|
+
lowpass_filter, highpass_filter = 1, 1
|
168
|
+
norm = float(sqrt(2 * log(2)))
|
169
|
+
upper_sampling = float(backend.max(backend.multiply(2, sampling_rate)))
|
170
|
+
|
171
|
+
if lowpass is not None:
|
172
|
+
lowpass = float(lowpass)
|
173
|
+
lowpass = backend.maximum(lowpass, backend.eps(backend._float_dtype))
|
174
|
+
if highpass is not None:
|
175
|
+
highpass = float(highpass)
|
176
|
+
highpass = backend.maximum(highpass, backend.eps(backend._float_dtype))
|
177
|
+
|
178
|
+
if lowpass is not None:
|
179
|
+
lowpass = upper_sampling / (lowpass * norm)
|
180
|
+
lowpass = backend.multiply(2, backend.square(lowpass))
|
181
|
+
lowpass_filter = backend.exp(backend.divide(grid, lowpass))
|
182
|
+
if highpass is not None:
|
183
|
+
highpass = upper_sampling / (highpass * norm)
|
184
|
+
highpass = backend.multiply(2, backend.square(highpass))
|
185
|
+
highpass_filter = 1 - backend.exp(backend.divide(grid, highpass))
|
186
|
+
|
187
|
+
bandpass_filter = backend.multiply(lowpass_filter, highpass_filter)
|
188
|
+
bandpass_filter = shift_fourier(
|
189
|
+
data=bandpass_filter, shape_is_real_fourier=shape_is_real_fourier
|
190
|
+
)
|
191
|
+
|
192
|
+
if return_real_fourier:
|
193
|
+
bandpass_filter = crop_real_fourier(bandpass_filter)
|
194
|
+
|
195
|
+
return bandpass_filter
|
196
|
+
|
197
|
+
def __call__(self, **kwargs):
|
198
|
+
func_args = vars(self)
|
199
|
+
func_args.update(kwargs)
|
200
|
+
|
201
|
+
func = self.discrete_bandpass
|
202
|
+
if func_args.get("use_gaussian"):
|
203
|
+
func = self.gaussian_bandpass
|
204
|
+
|
205
|
+
mask = func(**func_args)
|
206
|
+
|
207
|
+
return {
|
208
|
+
"data": backend.to_backend_array(mask),
|
209
|
+
"sampling_rate": func_args.get("sampling_rate", 1),
|
210
|
+
"is_multiplicative_filter": True,
|
211
|
+
}
|
212
|
+
|
213
|
+
|
214
|
+
class LinearWhiteningFilter:
|
215
|
+
"""
|
216
|
+
This class provides methods to compute the spectrum of the input data and
|
217
|
+
apply linear whitening to the Fourier coefficients.
|
218
|
+
|
219
|
+
Parameters:
|
220
|
+
-----------
|
221
|
+
**kwargs : Dict, optional
|
222
|
+
Additional keyword arguments.
|
223
|
+
|
224
|
+
|
225
|
+
References
|
226
|
+
----------
|
227
|
+
.. [1] de Teresa-Trueba, I.; Goetz, S. K.; Mattausch, A.; Stojanovska, F.; Zimmerli, C. E.;
|
228
|
+
Toro-Nahuelpan, M.; Cheng, D. W. C.; Tollervey, F.; Pape, C.; Beck, M.; Diz-Munoz,
|
229
|
+
A.; Kreshuk, A.; Mahamid, J.; Zaugg, J. B. Nat. Methods 2023, 20, 284–294.
|
230
|
+
.. [2] M. L. Chaillet, G. van der Schot, I. Gubins, S. Roet,
|
231
|
+
R. C. Veltkamp, and F. Förster, Int. J. Mol. Sci. 24,
|
232
|
+
13375 (2023)
|
233
|
+
"""
|
234
|
+
|
235
|
+
def __init__(self, **kwargs):
|
236
|
+
pass
|
237
|
+
|
238
|
+
@staticmethod
|
239
|
+
def _compute_spectrum(
|
240
|
+
data_rfft: NDArray, n_bins: int = None, batch_dimension: int = None
|
241
|
+
) -> Tuple[NDArray, NDArray]:
|
242
|
+
"""
|
243
|
+
Compute the spectrum of the input data.
|
244
|
+
|
245
|
+
Parameters:
|
246
|
+
-----------
|
247
|
+
data_rfft : NDArray
|
248
|
+
The Fourier transform of the input data.
|
249
|
+
n_bins : int, optional
|
250
|
+
The number of bins for computing the spectrum, defaults to None.
|
251
|
+
batch_dimension : int, optional
|
252
|
+
Batch dimension to average over.
|
253
|
+
|
254
|
+
Returns:
|
255
|
+
--------
|
256
|
+
bins : NDArray
|
257
|
+
Array containing the bin indices for the spectrum.
|
258
|
+
radial_averages : NDArray
|
259
|
+
Array containing the radial averages of the spectrum.
|
260
|
+
"""
|
261
|
+
shape = tuple(x for i, x in enumerate(data_rfft.shape) if i != batch_dimension)
|
262
|
+
|
263
|
+
max_bins = max(max(shape[:-1]) // 2 + 1, shape[-1])
|
264
|
+
n_bins = max_bins if n_bins is None else n_bins
|
265
|
+
n_bins = int(min(n_bins, max_bins))
|
266
|
+
|
267
|
+
bins = fftfreqn(
|
268
|
+
shape=shape,
|
269
|
+
sampling_rate=0.5,
|
270
|
+
shape_is_real_fourier=True,
|
271
|
+
compute_euclidean_norm=True,
|
272
|
+
)
|
273
|
+
bins = backend.to_numpy_array(bins)
|
274
|
+
|
275
|
+
# Implicit lowpass to nyquist
|
276
|
+
bins = np.floor(bins * (n_bins - 1) + 0.5).astype(int)
|
277
|
+
fft_shift_axes = tuple(
|
278
|
+
i for i in range(data_rfft.ndim - 1) if i != batch_dimension
|
279
|
+
)
|
280
|
+
fourier_spectrum = np.fft.fftshift(data_rfft, axes=fft_shift_axes)
|
281
|
+
fourier_spectrum = np.abs(fourier_spectrum)
|
282
|
+
np.square(fourier_spectrum, out=fourier_spectrum)
|
283
|
+
|
284
|
+
radial_averages = ndimean(
|
285
|
+
fourier_spectrum, labels=bins, index=np.arange(n_bins)
|
286
|
+
)
|
287
|
+
np.sqrt(radial_averages, out=radial_averages)
|
288
|
+
np.reciprocal(radial_averages, out=radial_averages)
|
289
|
+
np.divide(radial_averages, radial_averages.max(), out=radial_averages)
|
290
|
+
|
291
|
+
return bins, radial_averages
|
292
|
+
|
293
|
+
@staticmethod
|
294
|
+
def _interpolate_spectrum(
|
295
|
+
spectrum: NDArray,
|
296
|
+
shape: Tuple[int],
|
297
|
+
shape_is_real_fourier: bool = True,
|
298
|
+
order: int = 1,
|
299
|
+
) -> NDArray:
|
300
|
+
"""
|
301
|
+
References
|
302
|
+
----------
|
303
|
+
.. [1] M. L. Chaillet, G. van der Schot, I. Gubins, S. Roet,
|
304
|
+
R. C. Veltkamp, and F. Förster, Int. J. Mol. Sci. 24,
|
305
|
+
13375 (2023)
|
306
|
+
"""
|
307
|
+
grid = fftfreqn(
|
308
|
+
shape=shape,
|
309
|
+
sampling_rate=.5,
|
310
|
+
shape_is_real_fourier=shape_is_real_fourier,
|
311
|
+
compute_euclidean_norm=True,
|
312
|
+
)
|
313
|
+
grid = backend.to_numpy_array(grid)
|
314
|
+
np.multiply(grid, (spectrum.shape[0] - 1), out = grid) + 0.5
|
315
|
+
spectrum = map_coordinates(spectrum, grid.reshape(1, -1), order=order)
|
316
|
+
return spectrum.reshape(grid.shape)
|
317
|
+
|
318
|
+
def __call__(
|
319
|
+
self,
|
320
|
+
data: NDArray = None,
|
321
|
+
data_rfft: NDArray = None,
|
322
|
+
n_bins: int = None,
|
323
|
+
batch_dimension: int = None,
|
324
|
+
order: int = 1,
|
325
|
+
**kwargs: Dict,
|
326
|
+
) -> Dict:
|
327
|
+
"""
|
328
|
+
Apply linear whitening to the data and return the result.
|
329
|
+
|
330
|
+
Parameters:
|
331
|
+
-----------
|
332
|
+
data : NDArray, optional
|
333
|
+
The input data, defaults to None.
|
334
|
+
data_rfft : NDArray, optional
|
335
|
+
The Fourier transform of the input data, defaults to None.
|
336
|
+
n_bins : int, optional
|
337
|
+
The number of bins for computing the spectrum, defaults to None.
|
338
|
+
batch_dimension : int, optional
|
339
|
+
Batch dimension to average over.
|
340
|
+
order : int, optional
|
341
|
+
Interpolation order to use.
|
342
|
+
**kwargs : Dict
|
343
|
+
Additional keyword arguments.
|
344
|
+
|
345
|
+
Returns:
|
346
|
+
--------
|
347
|
+
Dict
|
348
|
+
Filter data and associated parameters.
|
349
|
+
"""
|
350
|
+
if data_rfft is None:
|
351
|
+
data_rfft = np.fft.rfftn(backend.to_numpy_array(data))
|
352
|
+
|
353
|
+
data_rfft = backend.to_numpy_array(data_rfft)
|
354
|
+
|
355
|
+
bins, radial_averages = self._compute_spectrum(
|
356
|
+
data_rfft, n_bins, batch_dimension
|
357
|
+
)
|
358
|
+
|
359
|
+
if order is None:
|
360
|
+
cutoff = bins < radial_averages.size
|
361
|
+
filter_mask = np.zeros(data_rfft.shape, radial_averages.dtype)
|
362
|
+
filter_mask[cutoff] = radial_averages[bins[cutoff]]
|
363
|
+
else:
|
364
|
+
filter_mask = self._interpolate_spectrum(
|
365
|
+
spectrum=radial_averages,
|
366
|
+
shape=data_rfft.shape,
|
367
|
+
shape_is_real_fourier=True,
|
368
|
+
)
|
369
|
+
|
370
|
+
filter_mask = np.fft.ifftshift(
|
371
|
+
filter_mask,
|
372
|
+
axes=tuple(i for i in range(data_rfft.ndim - 1) if i != batch_dimension)
|
373
|
+
)
|
374
|
+
|
375
|
+
return {
|
376
|
+
"data": backend.to_backend_array(filter_mask),
|
377
|
+
"is_multiplicative_filter": True,
|
378
|
+
}
|