pytme 0.3.1.post2__cp311-cp311-macosx_15_0_arm64.whl → 0.3.2.dev0__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.
- pytme-0.3.2.dev0.data/scripts/estimate_ram_usage.py +97 -0
- {pytme-0.3.1.post2.data → pytme-0.3.2.dev0.data}/scripts/match_template.py +213 -196
- {pytme-0.3.1.post2.data → pytme-0.3.2.dev0.data}/scripts/postprocess.py +40 -78
- {pytme-0.3.1.post2.data → pytme-0.3.2.dev0.data}/scripts/preprocess.py +4 -5
- {pytme-0.3.1.post2.data → pytme-0.3.2.dev0.data}/scripts/preprocessor_gui.py +49 -103
- {pytme-0.3.1.post2.data → pytme-0.3.2.dev0.data}/scripts/pytme_runner.py +46 -69
- {pytme-0.3.1.post2.dist-info → pytme-0.3.2.dev0.dist-info}/METADATA +2 -1
- pytme-0.3.2.dev0.dist-info/RECORD +136 -0
- scripts/estimate_ram_usage.py +97 -0
- scripts/match_template.py +213 -196
- scripts/match_template_devel.py +1339 -0
- scripts/postprocess.py +40 -78
- scripts/preprocess.py +4 -5
- scripts/preprocessor_gui.py +49 -103
- scripts/pytme_runner.py +46 -69
- tests/preprocessing/test_compose.py +31 -30
- tests/preprocessing/test_frequency_filters.py +17 -32
- tests/preprocessing/test_preprocessor.py +0 -19
- tests/preprocessing/test_utils.py +13 -1
- tests/test_analyzer.py +2 -10
- tests/test_backends.py +47 -18
- tests/test_density.py +72 -13
- tests/test_extensions.py +1 -0
- tests/test_matching_cli.py +23 -9
- tests/test_matching_exhaustive.py +5 -5
- tests/test_matching_utils.py +3 -3
- tests/test_orientations.py +12 -0
- tests/test_rotations.py +13 -23
- tests/test_structure.py +1 -7
- tme/__version__.py +1 -1
- tme/analyzer/aggregation.py +47 -16
- tme/analyzer/base.py +34 -0
- tme/analyzer/peaks.py +26 -13
- tme/analyzer/proxy.py +14 -0
- tme/backends/_jax_utils.py +91 -68
- tme/backends/cupy_backend.py +6 -19
- tme/backends/jax_backend.py +103 -98
- tme/backends/matching_backend.py +0 -17
- tme/backends/mlx_backend.py +0 -29
- tme/backends/npfftw_backend.py +100 -97
- tme/backends/pytorch_backend.py +65 -78
- tme/cli.py +2 -2
- tme/density.py +44 -57
- tme/extensions.cpython-311-darwin.so +0 -0
- tme/filters/_utils.py +52 -24
- tme/filters/bandpass.py +99 -105
- tme/filters/compose.py +133 -39
- tme/filters/ctf.py +51 -102
- tme/filters/reconstruction.py +67 -122
- tme/filters/wedge.py +296 -325
- tme/filters/whitening.py +39 -75
- tme/mask.py +2 -2
- tme/matching_data.py +87 -15
- tme/matching_exhaustive.py +70 -120
- tme/matching_optimization.py +9 -63
- tme/matching_scores.py +261 -100
- tme/matching_utils.py +150 -91
- tme/memory.py +1 -0
- tme/orientations.py +17 -3
- tme/preprocessor.py +0 -239
- tme/rotations.py +102 -70
- tme/structure.py +601 -631
- tme/types.py +1 -0
- pytme-0.3.1.post2.dist-info/RECORD +0 -133
- {pytme-0.3.1.post2.data → pytme-0.3.2.dev0.data}/scripts/estimate_memory_usage.py +0 -0
- {pytme-0.3.1.post2.dist-info → pytme-0.3.2.dev0.dist-info}/WHEEL +0 -0
- {pytme-0.3.1.post2.dist-info → pytme-0.3.2.dev0.dist-info}/entry_points.txt +0 -0
- {pytme-0.3.1.post2.dist-info → pytme-0.3.2.dev0.dist-info}/licenses/LICENSE +0 -0
- {pytme-0.3.1.post2.dist-info → pytme-0.3.2.dev0.dist-info}/top_level.txt +0 -0
tme/filters/wedge.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
"""
|
2
|
-
Implements class Wedge and WedgeReconstructed
|
3
|
-
filter representations.
|
2
|
+
Implements class Wedge and WedgeReconstructed.
|
4
3
|
|
5
4
|
Copyright (c) 2024 European Molecular Biology Laboratory
|
6
5
|
|
@@ -16,16 +15,14 @@ from ..types import NDArray
|
|
16
15
|
from ..backends import backend as be
|
17
16
|
from .compose import ComposableFilter
|
18
17
|
from ..matching_utils import center_slice
|
19
|
-
from ..rotations import euler_to_rotationmatrix
|
20
18
|
from ..parser import XMLParser, StarParser, MDOCParser
|
21
19
|
from ._utils import (
|
22
20
|
centered_grid,
|
23
21
|
frequency_grid_at_angle,
|
24
22
|
compute_tilt_shape,
|
25
|
-
crop_real_fourier,
|
26
23
|
fftfreqn,
|
27
|
-
shift_fourier,
|
28
24
|
create_reconstruction_filter,
|
25
|
+
shift_fourier,
|
29
26
|
)
|
30
27
|
|
31
28
|
__all__ = ["Wedge", "WedgeReconstructed"]
|
@@ -34,12 +31,10 @@ __all__ = ["Wedge", "WedgeReconstructed"]
|
|
34
31
|
@dataclass
|
35
32
|
class Wedge(ComposableFilter):
|
36
33
|
"""
|
37
|
-
|
34
|
+
Create per-tilt wedge mask for tomographic data.
|
38
35
|
"""
|
39
36
|
|
40
|
-
#:
|
41
|
-
shape: Tuple[int] = None
|
42
|
-
#: The tilt angles.
|
37
|
+
#: Tilt angles in degrees.
|
43
38
|
angles: Tuple[float] = None
|
44
39
|
#: The weights corresponding to each tilt angle.
|
45
40
|
weights: Tuple[float] = None
|
@@ -55,7 +50,7 @@ class Wedge(ComposableFilter):
|
|
55
50
|
sampling_rate: Tuple[float] = 1
|
56
51
|
|
57
52
|
@classmethod
|
58
|
-
def from_file(cls, filename: str) -> "Wedge":
|
53
|
+
def from_file(cls, filename: str, **kwargs) -> "Wedge":
|
59
54
|
"""
|
60
55
|
Generate a :py:class:`Wedge` instance by reading tilt angles and weights.
|
61
56
|
Supported extensions are:
|
@@ -100,180 +95,56 @@ class Wedge(ComposableFilter):
|
|
100
95
|
raise ValueError("Length of weights and angles differ.")
|
101
96
|
|
102
97
|
return cls(
|
103
|
-
shape=None,
|
104
98
|
tilt_axis=0,
|
105
99
|
opening_axis=2,
|
106
100
|
angles=np.array(angles, dtype=np.float32),
|
107
101
|
weights=np.array(weights, dtype=np.float32),
|
102
|
+
**kwargs,
|
108
103
|
)
|
109
104
|
|
110
|
-
def
|
111
|
-
"""
|
112
|
-
Returns a Wedge stack of chosen parameters with DC component in the center.
|
113
|
-
"""
|
114
|
-
func_args = vars(self).copy()
|
115
|
-
func_args.update(kwargs)
|
116
|
-
|
105
|
+
def _evaluate(self, shape: Tuple[int, ...], **kwargs: Dict) -> NDArray:
|
106
|
+
"""Returns a Wedge stack of chosen parameters."""
|
117
107
|
weight_types = {
|
118
|
-
None:
|
119
|
-
"angle":
|
120
|
-
"relion":
|
121
|
-
"grigorieff":
|
108
|
+
None: weight_uniform,
|
109
|
+
"angle": weight_angle,
|
110
|
+
"relion": weight_relion,
|
111
|
+
"grigorieff": weight_grigorieff,
|
122
112
|
}
|
123
113
|
|
124
|
-
weight_type =
|
114
|
+
weight_type = kwargs.get("weight_type", None)
|
125
115
|
if weight_type not in weight_types:
|
126
116
|
raise ValueError(
|
127
117
|
f"Supported weight_types are {','.join(list(weight_types.keys()))}"
|
128
118
|
)
|
129
119
|
|
130
120
|
if weight_type == "angle":
|
131
|
-
|
121
|
+
kwargs["weights"] = np.cos(np.radians(self.angles))
|
132
122
|
|
133
|
-
ret = weight_types[weight_type](**
|
123
|
+
ret = weight_types[weight_type](shape=shape, **kwargs)
|
134
124
|
|
135
|
-
frequency_cutoff =
|
125
|
+
frequency_cutoff = kwargs.get("frequency_cutoff", None)
|
136
126
|
if frequency_cutoff is not None:
|
137
|
-
for index, angle in enumerate(
|
127
|
+
for index, angle in enumerate(kwargs["angles"]):
|
138
128
|
frequency_grid = frequency_grid_at_angle(
|
139
|
-
shape=
|
140
|
-
opening_axis=
|
141
|
-
tilt_axis=
|
129
|
+
shape=shape,
|
130
|
+
opening_axis=kwargs["opening_axis"],
|
131
|
+
tilt_axis=kwargs["tilt_axis"],
|
142
132
|
angle=angle,
|
143
133
|
sampling_rate=1,
|
144
134
|
)
|
145
135
|
ret[index] = np.multiply(ret[index], frequency_grid <= frequency_cutoff)
|
146
136
|
|
147
137
|
ret = be.astype(be.to_backend_array(ret), be._float_dtype)
|
148
|
-
|
149
|
-
return {
|
150
|
-
"data": ret,
|
151
|
-
"shape": func_args["shape"],
|
152
|
-
"return_real_fourier": func_args.get("return_real_fourier", False),
|
153
|
-
"is_multiplicative_filter": True,
|
154
|
-
}
|
155
|
-
|
156
|
-
@staticmethod
|
157
|
-
def weight_angle(
|
158
|
-
shape: Tuple[int],
|
159
|
-
weights: Tuple[float],
|
160
|
-
angles: Tuple[float],
|
161
|
-
opening_axis: int,
|
162
|
-
tilt_axis: int,
|
163
|
-
**kwargs,
|
164
|
-
) -> NDArray:
|
165
|
-
"""
|
166
|
-
Generate weighted wedges based on the cosine of the current angle.
|
167
|
-
"""
|
168
|
-
tilt_shape = compute_tilt_shape(
|
169
|
-
shape=shape, opening_axis=opening_axis, reduce_dim=True
|
170
|
-
)
|
171
|
-
wedge, wedges = np.ones(tilt_shape), np.zeros((len(angles), *tilt_shape))
|
172
|
-
for index, angle in enumerate(angles):
|
173
|
-
wedge.fill(weights[index])
|
174
|
-
wedges[index] = wedge
|
175
|
-
|
176
|
-
return wedges
|
177
|
-
|
178
|
-
def weight_relion(
|
179
|
-
self,
|
180
|
-
shape: Tuple[int],
|
181
|
-
opening_axis: int,
|
182
|
-
tilt_axis: int,
|
183
|
-
sampling_rate: float = 1.0,
|
184
|
-
**kwargs,
|
185
|
-
) -> NDArray:
|
186
|
-
"""
|
187
|
-
Generate weighted wedges based on the RELION 1.4 formalism, weighting each
|
188
|
-
angle using the cosine of the angle and a Gaussian lowpass filter computed
|
189
|
-
with respect to the exposure per angstrom.
|
190
|
-
|
191
|
-
Returns
|
192
|
-
-------
|
193
|
-
NDArray
|
194
|
-
Weighted wedges.
|
195
|
-
"""
|
196
|
-
tilt_shape = compute_tilt_shape(
|
197
|
-
shape=shape, opening_axis=opening_axis, reduce_dim=True
|
198
|
-
)
|
199
|
-
wedges = np.zeros((len(self.angles), *tilt_shape))
|
200
|
-
for index, angle in enumerate(self.angles):
|
201
|
-
frequency_grid = frequency_grid_at_angle(
|
202
|
-
shape=shape,
|
203
|
-
opening_axis=opening_axis,
|
204
|
-
tilt_axis=tilt_axis,
|
205
|
-
angle=angle,
|
206
|
-
sampling_rate=sampling_rate,
|
207
|
-
)
|
208
|
-
sigma = np.sqrt(self.weights[index] * 4 / (8 * np.pi**2))
|
209
|
-
sigma = -2 * np.pi**2 * sigma**2
|
210
|
-
frequency_grid = np.square(frequency_grid, out=frequency_grid)
|
211
|
-
frequency_grid = np.multiply(sigma, frequency_grid, out=frequency_grid)
|
212
|
-
frequency_grid = np.exp(frequency_grid, out=frequency_grid)
|
213
|
-
wedges[index] = np.multiply(frequency_grid, np.cos(np.radians(angle)))
|
214
|
-
|
215
|
-
return wedges
|
216
|
-
|
217
|
-
def weight_grigorieff(
|
218
|
-
self,
|
219
|
-
shape: Tuple[int],
|
220
|
-
opening_axis: int,
|
221
|
-
tilt_axis: int,
|
222
|
-
amplitude: float = 0.245,
|
223
|
-
power: float = -1.665,
|
224
|
-
offset: float = 2.81,
|
225
|
-
sampling_rate: float = 1.0,
|
226
|
-
**kwargs,
|
227
|
-
) -> NDArray:
|
228
|
-
"""
|
229
|
-
Generate weighted wedges based on the formalism introduced in [1]_.
|
230
|
-
|
231
|
-
Returns
|
232
|
-
-------
|
233
|
-
NDArray
|
234
|
-
Weighted wedges.
|
235
|
-
|
236
|
-
References
|
237
|
-
----------
|
238
|
-
.. [1] Timothy Grant, Nikolaus Grigorieff (2015), eLife 4:e06980.
|
239
|
-
"""
|
240
|
-
tilt_shape = compute_tilt_shape(
|
241
|
-
shape=shape, opening_axis=opening_axis, reduce_dim=True
|
242
|
-
)
|
243
|
-
|
244
|
-
wedges = np.zeros((len(self.angles), *tilt_shape), dtype=be._float_dtype)
|
245
|
-
for index, angle in enumerate(self.angles):
|
246
|
-
frequency_grid = frequency_grid_at_angle(
|
247
|
-
shape=shape,
|
248
|
-
opening_axis=opening_axis,
|
249
|
-
tilt_axis=tilt_axis,
|
250
|
-
angle=angle,
|
251
|
-
sampling_rate=sampling_rate,
|
252
|
-
)
|
253
|
-
|
254
|
-
with np.errstate(divide="ignore"):
|
255
|
-
np.power(frequency_grid, power, out=frequency_grid)
|
256
|
-
np.multiply(amplitude, frequency_grid, out=frequency_grid)
|
257
|
-
np.add(frequency_grid, offset, out=frequency_grid)
|
258
|
-
np.multiply(-2, frequency_grid, out=frequency_grid)
|
259
|
-
np.divide(
|
260
|
-
self.weights[index],
|
261
|
-
frequency_grid,
|
262
|
-
out=frequency_grid,
|
263
|
-
)
|
264
|
-
|
265
|
-
wedges[index] = np.exp(frequency_grid)
|
266
|
-
|
267
|
-
return wedges
|
138
|
+
return {"data": ret, "shape": shape}
|
268
139
|
|
269
140
|
|
270
141
|
@dataclass
|
271
|
-
class WedgeReconstructed:
|
142
|
+
class WedgeReconstructed(Wedge):
|
272
143
|
"""
|
273
|
-
|
144
|
+
Create wedge mask for tomographic reconstructions.
|
274
145
|
"""
|
275
146
|
|
276
|
-
#:
|
147
|
+
#: Tilt angles in degrees.
|
277
148
|
angles: Tuple[float] = None
|
278
149
|
#: Weights to assign to individual wedge components. Not considered for continuous wedge
|
279
150
|
weights: Tuple[float] = None
|
@@ -290,22 +161,14 @@ class WedgeReconstructed:
|
|
290
161
|
#: Filter window applied during reconstruction.
|
291
162
|
reconstruction_filter: str = None
|
292
163
|
|
293
|
-
def
|
294
|
-
if self.create_continuous_wedge:
|
295
|
-
self.angles = (min(self.angles), max(self.angles))
|
296
|
-
|
297
|
-
def __call__(
|
298
|
-
self, shape: Tuple[int], return_real_fourier: bool = False, **kwargs
|
299
|
-
) -> Dict:
|
164
|
+
def _evaluate(self, shape: Tuple[int, ...], **kwargs) -> Dict:
|
300
165
|
"""
|
301
|
-
Generate
|
166
|
+
Generate a reconstructed wedge.
|
302
167
|
|
303
168
|
Parameters
|
304
169
|
----------
|
305
170
|
shape : tuple of int
|
306
|
-
The shape
|
307
|
-
return_real_fourier : tuple of int
|
308
|
-
Return a shape compliant with rfftn. Defaults to False.
|
171
|
+
The shape to build the filter for.
|
309
172
|
**kwargs : dict
|
310
173
|
Additional keyword arguments.
|
311
174
|
|
@@ -316,198 +179,306 @@ class WedgeReconstructed:
|
|
316
179
|
The filter mask.
|
317
180
|
shape: tuple of ints
|
318
181
|
The requested filter shape
|
319
|
-
return_real_fourier: bool
|
320
|
-
Whether data is compliant with rfftn.
|
321
|
-
is_multiplicative_filter: bool
|
322
|
-
Whether the filter is multiplicative in Fourier space.
|
323
182
|
"""
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
weight_wedge =
|
332
|
-
if
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
ret =
|
338
|
-
frequency_cutoff =
|
183
|
+
func = step_wedge
|
184
|
+
angles = kwargs.pop("angles", (0,))
|
185
|
+
if kwargs.get("create_continuous_wedge", False):
|
186
|
+
func = continuous_wedge
|
187
|
+
if len(angles) != 2:
|
188
|
+
angles = (min(angles), max(angles))
|
189
|
+
|
190
|
+
weight_wedge = kwargs.get("weight_wedge", False)
|
191
|
+
if kwargs.get("wedge_weights") is None and weight_wedge:
|
192
|
+
kwargs["weights"] = np.cos(np.radians(be.to_numpy_array(angles)))
|
193
|
+
ret = func(shape=shape, angles=angles, **kwargs)
|
194
|
+
|
195
|
+
# Move DC component to origin
|
196
|
+
ret = shift_fourier(ret, shape_is_real_fourier=False)
|
197
|
+
frequency_cutoff = kwargs.get("frequency_cutoff", None)
|
339
198
|
if frequency_cutoff is not None:
|
340
|
-
frequency_mask =
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
199
|
+
frequency_mask = (
|
200
|
+
fftfreqn(
|
201
|
+
shape=shape,
|
202
|
+
sampling_rate=1,
|
203
|
+
compute_euclidean_norm=True,
|
204
|
+
shape_is_real_fourier=False,
|
205
|
+
fftshift=False,
|
206
|
+
)
|
207
|
+
<= frequency_cutoff
|
345
208
|
)
|
346
|
-
ret = np.multiply(ret, frequency_mask
|
209
|
+
ret = np.multiply(ret, frequency_mask, out=ret)
|
347
210
|
|
348
211
|
if not weight_wedge:
|
349
212
|
ret = (ret > 0) * 1.0
|
350
|
-
|
351
213
|
ret = be.astype(be.to_backend_array(ret), be._float_dtype)
|
214
|
+
return {"data": ret, "shape": shape}
|
352
215
|
|
353
|
-
ret = shift_fourier(data=ret, shape_is_real_fourier=False)
|
354
216
|
|
355
|
-
|
356
|
-
|
217
|
+
def continuous_wedge(
|
218
|
+
shape: Tuple[int, ...],
|
219
|
+
angles: Tuple[float, float],
|
220
|
+
opening_axis: int,
|
221
|
+
tilt_axis: int,
|
222
|
+
**kwargs: Dict,
|
223
|
+
) -> NDArray:
|
224
|
+
"""
|
225
|
+
Generate a continous wedge mask with DC component at the center.
|
357
226
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
227
|
+
Parameters
|
228
|
+
----------
|
229
|
+
shape : tuple of int
|
230
|
+
The shape of the reconstruction volume.
|
231
|
+
angles : tuple of float
|
232
|
+
Start and stop tilt angle in degrees.
|
233
|
+
opening_axis : int
|
234
|
+
The axis around which the wedge is opened.
|
235
|
+
tilt_axis : int
|
236
|
+
The axis along which the tilt is applied.
|
364
237
|
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
)
|
373
|
-
|
374
|
-
|
238
|
+
Returns
|
239
|
+
-------
|
240
|
+
NDArray
|
241
|
+
Wedge mask.
|
242
|
+
"""
|
243
|
+
angles = np.abs(np.asarray(angles))
|
244
|
+
aspect_ratio = shape[opening_axis] / shape[tilt_axis]
|
245
|
+
angles = np.degrees(np.arctan(np.tan(np.radians(angles)) * aspect_ratio))
|
246
|
+
|
247
|
+
start_radians = np.tan(np.radians(90 - angles[0]))
|
248
|
+
stop_radians = np.tan(np.radians(-1 * (90 - angles[1])))
|
249
|
+
|
250
|
+
grid = centered_grid(shape)
|
251
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
252
|
+
ratios = np.where(
|
253
|
+
grid[opening_axis] == 0,
|
254
|
+
np.tan(np.radians(90)) + 1,
|
255
|
+
grid[tilt_axis] / grid[opening_axis],
|
256
|
+
)
|
375
257
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
The shape of the reconstruction volume.
|
380
|
-
angles : tuple of float
|
381
|
-
Start and stop tilt angle.
|
382
|
-
opening_axis : int
|
383
|
-
The axis around which the wedge is opened.
|
384
|
-
tilt_axis : int
|
385
|
-
The axis along which the tilt is applied.
|
258
|
+
wedge = np.logical_or(start_radians <= ratios, stop_radians >= ratios).astype(
|
259
|
+
np.float32
|
260
|
+
)
|
386
261
|
|
387
|
-
|
388
|
-
-------
|
389
|
-
NDArray
|
390
|
-
Wedge mask.
|
391
|
-
"""
|
392
|
-
angles = np.abs(np.asarray(angles))
|
393
|
-
aspect_ratio = shape[opening_axis] / shape[tilt_axis]
|
394
|
-
angles = np.degrees(np.arctan(np.tan(np.radians(angles)) * aspect_ratio))
|
395
|
-
|
396
|
-
start_radians = np.tan(np.radians(90 - angles[0]))
|
397
|
-
stop_radians = np.tan(np.radians(-1 * (90 - angles[1])))
|
398
|
-
|
399
|
-
grid = centered_grid(shape)
|
400
|
-
with np.errstate(divide="ignore", invalid="ignore"):
|
401
|
-
ratios = np.where(
|
402
|
-
grid[opening_axis] == 0,
|
403
|
-
np.tan(np.radians(90)) + 1,
|
404
|
-
grid[tilt_axis] / grid[opening_axis],
|
405
|
-
)
|
262
|
+
return wedge
|
406
263
|
|
407
|
-
wedge = np.logical_or(start_radians <= ratios, stop_radians >= ratios).astype(
|
408
|
-
np.float32
|
409
|
-
)
|
410
264
|
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
) -> NDArray:
|
423
|
-
"""
|
424
|
-
Generate a per-angle wedge shape with DC component at the center.
|
265
|
+
def step_wedge(
|
266
|
+
shape: Tuple[int, ...],
|
267
|
+
angles: Tuple[float, ...],
|
268
|
+
opening_axis: int,
|
269
|
+
tilt_axis: int,
|
270
|
+
weights: Tuple[float, ...] = None,
|
271
|
+
reconstruction_filter: str = None,
|
272
|
+
**kwargs: Dict,
|
273
|
+
) -> NDArray:
|
274
|
+
"""
|
275
|
+
Generate a per-angle wedge shape with DC component at the center.
|
425
276
|
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
277
|
+
Parameters
|
278
|
+
----------
|
279
|
+
shape : tuple of int
|
280
|
+
The shape of the reconstruction volume.
|
281
|
+
angles : tuple of float
|
282
|
+
The tilt angles in degrees.
|
283
|
+
opening_axis : int
|
284
|
+
The axis around which the wedge is opened.
|
285
|
+
tilt_axis : int
|
286
|
+
The axis along which the tilt is applied.
|
287
|
+
reconstruction_filter : str
|
288
|
+
Filter used during reconstruction.
|
289
|
+
weights : tuple of float, optional
|
290
|
+
Weights to assign to individual tilts. Defaults to 1.
|
440
291
|
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
292
|
+
Returns
|
293
|
+
-------
|
294
|
+
NDArray
|
295
|
+
Wege mask.
|
296
|
+
"""
|
297
|
+
from ..backends import NumpyFFTWBackend
|
298
|
+
|
299
|
+
angles = np.asarray(be.to_numpy_array(angles))
|
300
|
+
|
301
|
+
if weights is None:
|
302
|
+
weights = np.ones(angles.size)
|
303
|
+
weights = np.asarray(weights)
|
304
|
+
|
305
|
+
shape = tuple(int(x) for x in shape)
|
306
|
+
opening_axis, tilt_axis = int(opening_axis), int(tilt_axis)
|
307
|
+
|
308
|
+
weights = np.repeat(weights, angles.size // weights.size)
|
309
|
+
plane = np.zeros(
|
310
|
+
(shape[opening_axis], shape[tilt_axis] + (1 - shape[tilt_axis] % 2)),
|
311
|
+
dtype=np.float32,
|
312
|
+
)
|
313
|
+
|
314
|
+
aspect_ratio = plane.shape[0] / plane.shape[1]
|
315
|
+
angles = np.degrees(np.arctan(np.tan(np.radians(angles)) * aspect_ratio))
|
316
|
+
|
317
|
+
rec_filter = 1
|
318
|
+
if reconstruction_filter is not None:
|
319
|
+
rec_filter = create_reconstruction_filter(
|
320
|
+
plane.shape[::-1], filter_type=reconstruction_filter, tilt_angles=angles
|
321
|
+
).T
|
322
|
+
|
323
|
+
subset = tuple(
|
324
|
+
slice(None) if i != 0 else slice(x // 2, x // 2 + 1)
|
325
|
+
for i, x in enumerate(plane.shape)
|
326
|
+
)
|
327
|
+
plane_rotated, wedge_volume = np.zeros_like(plane), np.zeros_like(plane)
|
328
|
+
for index in range(angles.shape[0]):
|
329
|
+
plane_rotated.fill(0)
|
330
|
+
plane[subset] = 1
|
331
|
+
|
332
|
+
angle_rad = np.radians(angles[index])
|
333
|
+
rotation_matrix = np.array(
|
334
|
+
[
|
335
|
+
[np.cos(angle_rad), -np.sin(angle_rad)],
|
336
|
+
[np.sin(angle_rad), np.cos(angle_rad)],
|
337
|
+
]
|
338
|
+
)
|
339
|
+
# We want a push rotation but rigid transform assumes pull
|
340
|
+
NumpyFFTWBackend().rigid_transform(
|
341
|
+
arr=plane * rec_filter,
|
342
|
+
rotation_matrix=rotation_matrix.T,
|
343
|
+
out=plane_rotated,
|
344
|
+
use_geometric_center=True,
|
345
|
+
order=1,
|
346
|
+
)
|
347
|
+
wedge_volume += plane_rotated * weights[index]
|
447
348
|
|
448
|
-
|
349
|
+
subset = center_slice(wedge_volume.shape, (shape[opening_axis], shape[tilt_axis]))
|
350
|
+
wedge_volume = wedge_volume[subset]
|
449
351
|
|
450
|
-
|
451
|
-
weights = np.ones(angles.size)
|
452
|
-
weights = np.asarray(weights)
|
352
|
+
np.fmin(wedge_volume, np.max(weights), wedge_volume)
|
453
353
|
|
454
|
-
|
455
|
-
|
354
|
+
if opening_axis > tilt_axis:
|
355
|
+
wedge_volume = np.moveaxis(wedge_volume, 1, 0)
|
456
356
|
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
dtype=np.float32,
|
461
|
-
)
|
357
|
+
reshape_dimensions = tuple(
|
358
|
+
x if i in (opening_axis, tilt_axis) else 1 for i, x in enumerate(shape)
|
359
|
+
)
|
462
360
|
|
463
|
-
|
464
|
-
|
361
|
+
wedge_volume = wedge_volume.reshape(reshape_dimensions)
|
362
|
+
tile_dimensions = np.divide(shape, reshape_dimensions).astype(int)
|
363
|
+
return np.tile(wedge_volume, tile_dimensions)
|
465
364
|
|
466
|
-
rec_filter = 1
|
467
|
-
if reconstruction_filter is not None:
|
468
|
-
rec_filter = create_reconstruction_filter(
|
469
|
-
plane.shape[::-1], filter_type=reconstruction_filter, tilt_angles=angles
|
470
|
-
).T
|
471
365
|
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
for index in range(angles.shape[0]):
|
478
|
-
plane_rotated.fill(0)
|
479
|
-
plane[subset] = 1
|
480
|
-
rotation_matrix = euler_to_rotationmatrix((angles[index], 0))
|
481
|
-
rotation_matrix = rotation_matrix[np.ix_((0, 1), (0, 1))]
|
482
|
-
|
483
|
-
NumpyFFTWBackend().rigid_transform(
|
484
|
-
arr=plane * rec_filter,
|
485
|
-
rotation_matrix=rotation_matrix,
|
486
|
-
out=plane_rotated,
|
487
|
-
use_geometric_center=True,
|
488
|
-
order=1,
|
489
|
-
)
|
490
|
-
wedge_volume += plane_rotated * weights[index]
|
366
|
+
def weight_uniform(angles: Tuple[float, ...], *args, **kwargs) -> NDArray:
|
367
|
+
"""
|
368
|
+
Generate uniform weighted wedges.
|
369
|
+
"""
|
370
|
+
return weight_angle(angles=np.zeros_like(angles), *args, **kwargs)
|
491
371
|
|
492
|
-
subset = center_slice(
|
493
|
-
wedge_volume.shape, (shape[opening_axis], shape[tilt_axis])
|
494
|
-
)
|
495
|
-
wedge_volume = wedge_volume[subset]
|
496
372
|
|
497
|
-
|
373
|
+
def weight_angle(
|
374
|
+
shape: Tuple[int, ...],
|
375
|
+
angles: Tuple[float, ...],
|
376
|
+
opening_axis: int,
|
377
|
+
tilt_axis: int,
|
378
|
+
**kwargs,
|
379
|
+
) -> NDArray:
|
380
|
+
"""
|
381
|
+
Generate weighted wedges based on the cosine of the current angle.
|
382
|
+
"""
|
383
|
+
tilt_shape = compute_tilt_shape(
|
384
|
+
shape=shape, opening_axis=opening_axis, reduce_dim=True
|
385
|
+
)
|
386
|
+
wedges = np.zeros((len(angles), *tilt_shape))
|
387
|
+
for index, angle in enumerate(angles):
|
388
|
+
wedges[index] = np.cos(np.radians(angle))
|
389
|
+
return wedges
|
390
|
+
|
391
|
+
|
392
|
+
def weight_relion(
|
393
|
+
shape: Tuple[int, ...],
|
394
|
+
angles: Tuple[float, ...],
|
395
|
+
weights: Tuple[float, ...],
|
396
|
+
opening_axis: int,
|
397
|
+
tilt_axis: int,
|
398
|
+
sampling_rate: float = 1.0,
|
399
|
+
**kwargs,
|
400
|
+
) -> NDArray:
|
401
|
+
"""
|
402
|
+
Generate weighted wedges based on the RELION 1.4 formalism, weighting each
|
403
|
+
angle using the cosine of the angle and a Gaussian lowpass filter computed
|
404
|
+
with respect to the exposure per angstrom.
|
498
405
|
|
499
|
-
|
500
|
-
|
406
|
+
Returns
|
407
|
+
-------
|
408
|
+
NDArray
|
409
|
+
Weighted wedges.
|
410
|
+
"""
|
411
|
+
tilt_shape = compute_tilt_shape(
|
412
|
+
shape=shape, opening_axis=opening_axis, reduce_dim=True
|
413
|
+
)
|
414
|
+
wedges = np.zeros((len(angles), *tilt_shape))
|
415
|
+
for index, angle in enumerate(angles):
|
416
|
+
frequency_grid = frequency_grid_at_angle(
|
417
|
+
shape=shape,
|
418
|
+
opening_axis=opening_axis,
|
419
|
+
tilt_axis=tilt_axis,
|
420
|
+
angle=angle,
|
421
|
+
sampling_rate=sampling_rate,
|
422
|
+
fftshift=False,
|
423
|
+
)
|
424
|
+
sigma = np.sqrt(weights[index] * 4 / (8 * np.pi**2))
|
425
|
+
sigma = -2 * np.pi**2 * sigma**2
|
426
|
+
frequency_grid = np.square(frequency_grid, out=frequency_grid)
|
427
|
+
frequency_grid = np.multiply(sigma, frequency_grid, out=frequency_grid)
|
428
|
+
frequency_grid = np.exp(frequency_grid, out=frequency_grid)
|
429
|
+
wedges[index] = np.multiply(frequency_grid, np.cos(np.radians(angle)))
|
430
|
+
|
431
|
+
return wedges
|
432
|
+
|
433
|
+
|
434
|
+
def weight_grigorieff(
|
435
|
+
shape: Tuple[int, ...],
|
436
|
+
angles: Tuple[float, ...],
|
437
|
+
weights: Tuple[float, ...],
|
438
|
+
opening_axis: int,
|
439
|
+
tilt_axis: int,
|
440
|
+
amplitude: float = 0.245,
|
441
|
+
power: float = -1.665,
|
442
|
+
offset: float = 2.81,
|
443
|
+
sampling_rate: float = 1.0,
|
444
|
+
**kwargs,
|
445
|
+
) -> NDArray:
|
446
|
+
"""
|
447
|
+
Generate weighted wedges based on the formalism introduced in [1]_.
|
501
448
|
|
502
|
-
|
503
|
-
|
449
|
+
Returns
|
450
|
+
-------
|
451
|
+
NDArray
|
452
|
+
Weighted wedges.
|
453
|
+
|
454
|
+
References
|
455
|
+
----------
|
456
|
+
.. [1] Timothy Grant, Nikolaus Grigorieff (2015), eLife 4:e06980.
|
457
|
+
"""
|
458
|
+
tilt_shape = compute_tilt_shape(
|
459
|
+
shape=shape, opening_axis=opening_axis, reduce_dim=True
|
460
|
+
)
|
461
|
+
|
462
|
+
wedges = np.zeros((len(angles), *tilt_shape), dtype=be._float_dtype)
|
463
|
+
for index, angle in enumerate(angles):
|
464
|
+
frequency_grid = frequency_grid_at_angle(
|
465
|
+
shape=shape,
|
466
|
+
opening_axis=opening_axis,
|
467
|
+
tilt_axis=tilt_axis,
|
468
|
+
angle=angle,
|
469
|
+
sampling_rate=sampling_rate,
|
470
|
+
fftshift=False,
|
504
471
|
)
|
505
472
|
|
506
|
-
|
507
|
-
|
508
|
-
|
473
|
+
with np.errstate(divide="ignore"):
|
474
|
+
np.power(frequency_grid, power, out=frequency_grid)
|
475
|
+
np.multiply(amplitude, frequency_grid, out=frequency_grid)
|
476
|
+
np.add(frequency_grid, offset, out=frequency_grid)
|
477
|
+
np.multiply(-2, frequency_grid, out=frequency_grid)
|
478
|
+
np.divide(weights[index], frequency_grid, out=frequency_grid)
|
479
|
+
wedges[index] = np.exp(frequency_grid)
|
509
480
|
|
510
|
-
|
481
|
+
return wedges
|
511
482
|
|
512
483
|
|
513
484
|
def _from_xml(filename: str, **kwargs) -> Dict:
|