pytme 0.2.2__cp311-cp311-macosx_14_0_arm64.whl → 0.2.4__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.2.data → pytme-0.2.4.data}/scripts/match_template.py +97 -148
- {pytme-0.2.2.data → pytme-0.2.4.data}/scripts/postprocess.py +20 -29
- pytme-0.2.4.data/scripts/preprocess.py +148 -0
- {pytme-0.2.2.data → pytme-0.2.4.data}/scripts/preprocessor_gui.py +15 -23
- {pytme-0.2.2.dist-info → pytme-0.2.4.dist-info}/METADATA +11 -10
- pytme-0.2.4.dist-info/RECORD +119 -0
- {pytme-0.2.2.dist-info → pytme-0.2.4.dist-info}/WHEEL +1 -1
- {pytme-0.2.2.dist-info → pytme-0.2.4.dist-info}/top_level.txt +1 -0
- pytme-0.2.2.data/scripts/preprocess.py → scripts/eval.py +1 -1
- scripts/match_template.py +97 -148
- scripts/postprocess.py +20 -29
- scripts/preprocess.py +116 -61
- scripts/preprocessor_gui.py +15 -23
- tests/__init__.py +0 -0
- tests/data/.DS_Store +0 -0
- tests/data/Blurring/.DS_Store +0 -0
- tests/data/Blurring/blob_width18.npy +0 -0
- tests/data/Blurring/edgegaussian_sigma3.npy +0 -0
- tests/data/Blurring/gaussian_sigma2.npy +0 -0
- tests/data/Blurring/hamming_width6.npy +0 -0
- tests/data/Blurring/kaiserb_width18.npy +0 -0
- tests/data/Blurring/localgaussian_sigma0510.npy +0 -0
- tests/data/Blurring/mean_size5.npy +0 -0
- tests/data/Blurring/ntree_sigma0510.npy +0 -0
- tests/data/Blurring/rank_rank3.npy +0 -0
- tests/data/Maps/.DS_Store +0 -0
- tests/data/Maps/emd_8621.mrc.gz +0 -0
- tests/data/README.md +2 -0
- tests/data/Raw/.DS_Store +0 -0
- tests/data/Raw/em_map.map +0 -0
- tests/data/Structures/.DS_Store +0 -0
- tests/data/Structures/1pdj.cif +3339 -0
- tests/data/Structures/1pdj.pdb +1429 -0
- tests/data/Structures/5khe.cif +3685 -0
- tests/data/Structures/5khe.ent +2210 -0
- tests/data/Structures/5khe.pdb +2210 -0
- tests/data/Structures/5uz4.cif +70548 -0
- tests/preprocessing/__init__.py +0 -0
- tests/preprocessing/test_compose.py +76 -0
- tests/preprocessing/test_frequency_filters.py +178 -0
- tests/preprocessing/test_preprocessor.py +136 -0
- tests/preprocessing/test_utils.py +79 -0
- tests/test_analyzer.py +310 -0
- tests/test_backends.py +375 -0
- tests/test_density.py +508 -0
- tests/test_extensions.py +130 -0
- tests/test_matching_cli.py +283 -0
- tests/test_matching_data.py +162 -0
- tests/test_matching_exhaustive.py +162 -0
- tests/test_matching_memory.py +30 -0
- tests/test_matching_optimization.py +276 -0
- tests/test_matching_utils.py +326 -0
- tests/test_orientations.py +173 -0
- tests/test_packaging.py +95 -0
- tests/test_parser.py +33 -0
- tests/test_structure.py +243 -0
- tme/__init__.py +0 -1
- tme/__version__.py +1 -1
- tme/analyzer.py +9 -6
- tme/backends/__init__.py +1 -1
- tme/backends/_jax_utils.py +10 -8
- tme/backends/cupy_backend.py +2 -7
- tme/backends/jax_backend.py +35 -20
- tme/backends/npfftw_backend.py +3 -2
- tme/backends/pytorch_backend.py +10 -7
- tme/data/scattering_factors.pickle +0 -0
- tme/density.py +26 -12
- tme/extensions.cpython-311-darwin.so +0 -0
- tme/external/bindings.cpp +332 -0
- tme/matching_data.py +33 -24
- tme/matching_exhaustive.py +39 -20
- tme/matching_scores.py +5 -2
- tme/matching_utils.py +8 -2
- tme/orientations.py +26 -9
- tme/preprocessing/_utils.py +14 -14
- tme/preprocessing/composable_filter.py +5 -4
- tme/preprocessing/compose.py +4 -4
- tme/preprocessing/frequency_filters.py +32 -35
- tme/preprocessing/tilt_series.py +210 -148
- tme/preprocessor.py +24 -246
- tme/structure.py +14 -14
- pytme-0.2.2.dist-info/RECORD +0 -74
- tme/matching_memory.py +0 -383
- {pytme-0.2.2.data → pytme-0.2.4.data}/scripts/estimate_ram_usage.py +0 -0
- {pytme-0.2.2.dist-info → pytme-0.2.4.dist-info}/LICENSE +0 -0
- {pytme-0.2.2.dist-info → pytme-0.2.4.dist-info}/entry_points.txt +0 -0
tme/preprocessing/tilt_series.py
CHANGED
@@ -10,11 +10,11 @@ from dataclasses import dataclass
|
|
10
10
|
|
11
11
|
import numpy as np
|
12
12
|
|
13
|
-
from .. import Preprocessor
|
14
13
|
from ..types import NDArray
|
15
14
|
from ..backends import backend as be
|
16
|
-
from ..matching_utils import euler_to_rotationmatrix
|
15
|
+
from ..matching_utils import euler_to_rotationmatrix, centered
|
17
16
|
from ._utils import (
|
17
|
+
centered_grid,
|
18
18
|
frequency_grid_at_angle,
|
19
19
|
compute_tilt_shape,
|
20
20
|
crop_real_fourier,
|
@@ -90,14 +90,10 @@ def create_reconstruction_filter(
|
|
90
90
|
tilt_angles = kwargs.get("tilt_angles", False)
|
91
91
|
if tilt_angles is False:
|
92
92
|
raise ValueError("'ramp' filter requires specifying tilt angles.")
|
93
|
-
size
|
94
|
-
ret =
|
95
|
-
ret /= size // 2
|
96
|
-
ret *= 0.5
|
97
|
-
np.abs(ret, out=ret)
|
98
|
-
|
93
|
+
size = filter_shape[0]
|
94
|
+
ret = fftfreqn((size,), sampling_rate = 1, compute_euclidean_norm = True)
|
99
95
|
min_increment = np.radians(np.min(np.abs(np.diff(np.sort(tilt_angles)))))
|
100
|
-
ret *= min_increment * size
|
96
|
+
ret *= (min_increment * size)
|
101
97
|
np.fmin(ret, 1, out=ret)
|
102
98
|
|
103
99
|
ret = np.tile(ret[:, np.newaxis], (1, filter_shape[1]))
|
@@ -164,8 +160,8 @@ class ReconstructFromTilt:
|
|
164
160
|
"""
|
165
161
|
Reconstruct a volume from a tilt series.
|
166
162
|
|
167
|
-
Parameters
|
168
|
-
|
163
|
+
Parameters
|
164
|
+
----------
|
169
165
|
data : NDArray
|
170
166
|
The tilt series data.
|
171
167
|
shape : tuple of int
|
@@ -177,15 +173,15 @@ class ReconstructFromTilt:
|
|
177
173
|
tilt_axis : int
|
178
174
|
Axis the plane is tilted over.
|
179
175
|
interpolation_order : int, optional
|
180
|
-
Interpolation order used for rotation,
|
176
|
+
Interpolation order used for rotation, defaults to 1.
|
181
177
|
return_real_fourier : bool, optional
|
182
178
|
Whether to return a shape compliant with rfftn, defaults to True.
|
183
179
|
reconstruction_filter : bool, optional
|
184
180
|
Filter window applied during reconstruction.
|
185
181
|
See :py:meth:`create_reconstruction_filter` for available options.
|
186
182
|
|
187
|
-
Returns
|
188
|
-
|
183
|
+
Returns
|
184
|
+
-------
|
189
185
|
NDArray
|
190
186
|
The reconstructed volume.
|
191
187
|
"""
|
@@ -197,7 +193,7 @@ class ReconstructFromTilt:
|
|
197
193
|
volume_temp_rotated = be.zeros(shape, dtype=be._float_dtype)
|
198
194
|
volume = be.zeros(shape, dtype=be._float_dtype)
|
199
195
|
|
200
|
-
slices = tuple(slice(a, a + 1) for a in
|
196
|
+
slices = tuple(slice(a//2, (a//2) + 1) for a in shape)
|
201
197
|
subset = tuple(
|
202
198
|
slice(None) if i != opening_axis else slices[opening_axis]
|
203
199
|
for i in range(len(shape))
|
@@ -224,6 +220,7 @@ class ReconstructFromTilt:
|
|
224
220
|
rec_filter = be.to_backend_array(rec_filter)
|
225
221
|
rec_filter = be.reshape(rec_filter, wedges[0].shape)
|
226
222
|
|
223
|
+
angles = be.to_backend_array(angles)
|
227
224
|
for index in range(len(angles)):
|
228
225
|
be.fill(angles_loop, 0)
|
229
226
|
be.fill(volume_temp, 0)
|
@@ -257,8 +254,8 @@ class Wedge:
|
|
257
254
|
"""
|
258
255
|
Generate wedge mask for tomographic data.
|
259
256
|
|
260
|
-
Parameters
|
261
|
-
|
257
|
+
Parameters
|
258
|
+
----------
|
262
259
|
shape : tuple of int
|
263
260
|
The shape of the reconstruction volume.
|
264
261
|
tilt_axis : int
|
@@ -272,10 +269,10 @@ class Wedge:
|
|
272
269
|
weight_type : str, optional
|
273
270
|
The type of weighting to apply, defaults to None.
|
274
271
|
frequency_cutoff : float, optional
|
275
|
-
|
272
|
+
Frequency cutoff for created mask. Nyquist 0.5 by default.
|
276
273
|
|
277
|
-
Returns
|
278
|
-
|
274
|
+
Returns
|
275
|
+
-------
|
279
276
|
Dict
|
280
277
|
A dictionary containing weighted wedges and related information.
|
281
278
|
"""
|
@@ -303,13 +300,13 @@ class Wedge:
|
|
303
300
|
Generate a :py:class:`Wedge` instance by reading tilt angles and weights
|
304
301
|
from a tab-separated text file.
|
305
302
|
|
306
|
-
Parameters
|
307
|
-
|
303
|
+
Parameters
|
304
|
+
----------
|
308
305
|
filename : str
|
309
306
|
The path to the file containing tilt angles and weights.
|
310
307
|
|
311
|
-
Returns
|
312
|
-
|
308
|
+
Returns
|
309
|
+
-------
|
313
310
|
:py:class:`Wedge`
|
314
311
|
Class instance instance initialized with angles and weights from the file.
|
315
312
|
"""
|
@@ -338,15 +335,15 @@ class Wedge:
|
|
338
335
|
"""
|
339
336
|
Read column data from a text file.
|
340
337
|
|
341
|
-
Parameters
|
342
|
-
|
338
|
+
Parameters
|
339
|
+
----------
|
343
340
|
filename : str
|
344
341
|
The path to the text file.
|
345
342
|
delimiter : str, optional
|
346
343
|
The delimiter used in the file, defaults to '\t'.
|
347
344
|
|
348
|
-
Returns
|
349
|
-
|
345
|
+
Returns
|
346
|
+
-------
|
350
347
|
Dict
|
351
348
|
A dictionary with one key for each column.
|
352
349
|
"""
|
@@ -380,6 +377,19 @@ class Wedge:
|
|
380
377
|
func_args["weights"] = np.cos(np.radians(self.angles))
|
381
378
|
|
382
379
|
ret = weight_types[weight_type](**func_args)
|
380
|
+
|
381
|
+
frequency_cutoff = func_args.get("frequency_cutoff", None)
|
382
|
+
if frequency_cutoff is not None:
|
383
|
+
for index, angle in enumerate(self.angles):
|
384
|
+
frequency_grid = frequency_grid_at_angle(
|
385
|
+
shape=func_args["shape"],
|
386
|
+
opening_axis=self.opening_axis,
|
387
|
+
tilt_axis=self.tilt_axis,
|
388
|
+
angle=angle,
|
389
|
+
sampling_rate=1,
|
390
|
+
)
|
391
|
+
ret[index] = np.multiply(ret[index], frequency_grid <= frequency_cutoff)
|
392
|
+
|
383
393
|
ret = be.astype(be.to_backend_array(ret), be._float_dtype)
|
384
394
|
|
385
395
|
return {
|
@@ -413,46 +423,50 @@ class Wedge:
|
|
413
423
|
|
414
424
|
return wedges
|
415
425
|
|
416
|
-
def weight_relion(self,
|
426
|
+
def weight_relion(self,
|
427
|
+
shape: Tuple[int],
|
428
|
+
opening_axis: int,
|
429
|
+
tilt_axis: int,
|
430
|
+
**kwargs
|
431
|
+
) -> NDArray:
|
417
432
|
"""
|
418
433
|
Generate weighted wedges based on the RELION 1.4 formalism, weighting each
|
419
434
|
angle using the cosine of the angle and a Gaussian lowpass filter computed
|
420
435
|
with respect to the exposure per angstrom.
|
421
436
|
|
422
|
-
Returns
|
423
|
-
|
437
|
+
Returns
|
438
|
+
-------
|
424
439
|
NDArray
|
425
440
|
Weighted wedges.
|
426
441
|
"""
|
427
442
|
tilt_shape = compute_tilt_shape(
|
428
|
-
shape=
|
443
|
+
shape=shape, opening_axis=opening_axis, reduce_dim=True
|
429
444
|
)
|
430
445
|
|
431
446
|
wedges = np.zeros((len(self.angles), *tilt_shape))
|
432
447
|
for index, angle in enumerate(self.angles):
|
433
448
|
frequency_grid = frequency_grid_at_angle(
|
434
|
-
shape=
|
435
|
-
opening_axis=
|
436
|
-
tilt_axis=
|
449
|
+
shape=shape,
|
450
|
+
opening_axis=opening_axis,
|
451
|
+
tilt_axis=tilt_axis,
|
437
452
|
angle=angle,
|
438
453
|
sampling_rate=1,
|
439
454
|
)
|
440
|
-
# frequency_mask = frequency_grid <= self.frequency_cutoff
|
441
|
-
|
442
455
|
sigma = np.sqrt(self.weights[index] * 4 / (8 * np.pi**2))
|
443
456
|
sigma = -2 * np.pi**2 * sigma**2
|
444
457
|
np.square(frequency_grid, out=frequency_grid)
|
445
458
|
np.multiply(sigma, frequency_grid, out=frequency_grid)
|
446
459
|
np.exp(frequency_grid, out=frequency_grid)
|
447
460
|
np.multiply(frequency_grid, np.cos(np.radians(angle)), out=frequency_grid)
|
448
|
-
# np.multiply(frequency_grid, frequency_mask, out=frequency_grid)
|
449
|
-
|
450
461
|
wedges[index] = frequency_grid
|
451
462
|
|
452
463
|
return wedges
|
453
464
|
|
454
465
|
def weight_grigorieff(
|
455
466
|
self,
|
467
|
+
shape: Tuple[int],
|
468
|
+
opening_axis: int,
|
469
|
+
tilt_axis: int,
|
456
470
|
amplitude: float = 0.245,
|
457
471
|
power: float = -1.665,
|
458
472
|
offset: float = 2.81,
|
@@ -461,31 +475,28 @@ class Wedge:
|
|
461
475
|
"""
|
462
476
|
Generate weighted wedges based on the formalism introduced in [1]_.
|
463
477
|
|
464
|
-
Returns
|
465
|
-
|
478
|
+
Returns
|
479
|
+
-------
|
466
480
|
NDArray
|
467
481
|
Weighted wedges.
|
468
482
|
|
469
483
|
References
|
470
484
|
----------
|
471
|
-
.. [1] Timothy
|
485
|
+
.. [1] Timothy Grant, Nikolaus Grigorieff (2015), eLife 4:e06980.
|
472
486
|
"""
|
473
487
|
tilt_shape = compute_tilt_shape(
|
474
|
-
shape=
|
475
|
-
opening_axis=self.opening_axis,
|
476
|
-
reduce_dim=True,
|
488
|
+
shape=shape, opening_axis=opening_axis, reduce_dim=True
|
477
489
|
)
|
478
490
|
|
479
491
|
wedges = np.zeros((len(self.angles), *tilt_shape), dtype=be._float_dtype)
|
480
492
|
for index, angle in enumerate(self.angles):
|
481
493
|
frequency_grid = frequency_grid_at_angle(
|
482
|
-
shape=
|
483
|
-
opening_axis=
|
484
|
-
tilt_axis=
|
494
|
+
shape=shape,
|
495
|
+
opening_axis=opening_axis,
|
496
|
+
tilt_axis=tilt_axis,
|
485
497
|
angle=angle,
|
486
498
|
sampling_rate=1,
|
487
499
|
)
|
488
|
-
# frequency_mask = frequency_grid <= self.frequency_cutoff
|
489
500
|
|
490
501
|
with np.errstate(divide="ignore"):
|
491
502
|
np.power(frequency_grid, power, out=frequency_grid)
|
@@ -498,10 +509,7 @@ class Wedge:
|
|
498
509
|
out=frequency_grid,
|
499
510
|
)
|
500
511
|
|
501
|
-
np.exp(frequency_grid
|
502
|
-
# np.multiply(frequency_grid, frequency_mask, out=frequency_grid)
|
503
|
-
|
504
|
-
wedges[index] = frequency_grid
|
512
|
+
wedges[index] = np.exp(frequency_grid)
|
505
513
|
|
506
514
|
return wedges
|
507
515
|
|
@@ -510,14 +518,24 @@ class WedgeReconstructed:
|
|
510
518
|
"""
|
511
519
|
Initialize :py:class:`WedgeReconstructed`.
|
512
520
|
|
513
|
-
Parameters
|
514
|
-
|
515
|
-
angles :
|
521
|
+
Parameters
|
522
|
+
----------
|
523
|
+
angles :tuple of float, optional
|
516
524
|
The tilt angles, defaults to None.
|
517
525
|
opening_axis : int, optional
|
518
526
|
The axis around which the wedge is opened, defaults to 0.
|
519
527
|
tilt_axis : int, optional
|
520
528
|
The axis along which the tilt is applied, defaults to 2.
|
529
|
+
weights : tuple of float, optional
|
530
|
+
Weights to assign to individual wedge components.
|
531
|
+
weight_wedge : bool, optional
|
532
|
+
Whether individual wedge components should be weighted. If True and weights
|
533
|
+
is None, uses the cosine of the angle otherwise weights.
|
534
|
+
create_continuous_wedge: bool, optional
|
535
|
+
Whether to create a continous wedge or a per-component wedge. Weights are only
|
536
|
+
considered for non-continuous wedges.
|
537
|
+
frequency_cutoff : float, optional
|
538
|
+
Filter window applied during reconstruction.
|
521
539
|
**kwargs : Dict
|
522
540
|
Additional keyword arguments.
|
523
541
|
"""
|
@@ -525,33 +543,37 @@ class WedgeReconstructed:
|
|
525
543
|
def __init__(
|
526
544
|
self,
|
527
545
|
angles: Tuple[float] = None,
|
528
|
-
start_tilt: float = None,
|
529
|
-
stop_tilt: float = None,
|
530
546
|
opening_axis: int = 0,
|
531
547
|
tilt_axis: int = 2,
|
548
|
+
weights : Tuple[float] = None,
|
532
549
|
weight_wedge: bool = False,
|
533
550
|
create_continuous_wedge: bool = False,
|
551
|
+
frequency_cutoff: float = 0.5,
|
552
|
+
reconstruction_filter: str = None,
|
534
553
|
**kwargs: Dict,
|
535
554
|
):
|
536
555
|
self.angles = angles
|
537
556
|
self.opening_axis = opening_axis
|
538
557
|
self.tilt_axis = tilt_axis
|
558
|
+
self.weights = weights
|
539
559
|
self.weight_wedge = weight_wedge
|
560
|
+
self.reconstruction_filter = reconstruction_filter
|
540
561
|
self.create_continuous_wedge = create_continuous_wedge
|
562
|
+
self.frequency_cutoff = frequency_cutoff
|
541
563
|
|
542
564
|
def __call__(self, shape: Tuple[int], **kwargs: Dict) -> Dict:
|
543
565
|
"""
|
544
566
|
Generate the reconstructed wedge.
|
545
567
|
|
546
|
-
Parameters
|
547
|
-
|
568
|
+
Parameters
|
569
|
+
----------
|
548
570
|
shape : tuple of int
|
549
571
|
The shape of the reconstruction volume.
|
550
572
|
**kwargs : Dict
|
551
573
|
Additional keyword arguments.
|
552
574
|
|
553
|
-
Returns
|
554
|
-
|
575
|
+
Returns
|
576
|
+
-------
|
555
577
|
Dict
|
556
578
|
A dictionary containing the reconstructed wedge and related information.
|
557
579
|
"""
|
@@ -559,15 +581,39 @@ class WedgeReconstructed:
|
|
559
581
|
func_args.update(kwargs)
|
560
582
|
|
561
583
|
if kwargs.get("is_fourier_shape", False):
|
562
|
-
print("Cannot create continuous wedge mask
|
584
|
+
print("Cannot create continuous wedge mask based on real fourier shape.")
|
563
585
|
|
564
586
|
func = self.step_wedge
|
565
587
|
if func_args.get("create_continuous_wedge", False):
|
566
588
|
func = self.continuous_wedge
|
567
589
|
|
590
|
+
weight_wedge = func_args.get("weight_wedge", False)
|
591
|
+
if func_args.get("wedge_weights") is None and weight_wedge:
|
592
|
+
func_args["weights"] = np.cos(
|
593
|
+
np.radians(be.to_numpy_array(func_args.get("angles", (0,))))
|
594
|
+
)
|
595
|
+
|
568
596
|
ret = func(shape=shape, **func_args)
|
597
|
+
|
598
|
+
frequency_cutoff = func_args.get("frequency_cutoff", None)
|
599
|
+
if frequency_cutoff is not None:
|
600
|
+
frequency_mask = fftfreqn(
|
601
|
+
shape=shape,
|
602
|
+
sampling_rate=1,
|
603
|
+
compute_euclidean_norm=True,
|
604
|
+
shape_is_real_fourier=False,
|
605
|
+
)
|
606
|
+
ret = np.multiply(ret, frequency_mask <= frequency_cutoff, out=ret)
|
607
|
+
|
608
|
+
if not weight_wedge:
|
609
|
+
ret = (ret > 0) * 1.0
|
610
|
+
|
569
611
|
ret = be.astype(be.to_backend_array(ret), be._float_dtype)
|
570
612
|
|
613
|
+
ret = shift_fourier(data=ret, shape_is_real_fourier=False)
|
614
|
+
if func_args.get("return_real_fourier", False):
|
615
|
+
ret = crop_real_fourier(ret)
|
616
|
+
|
571
617
|
return {
|
572
618
|
"data": ret,
|
573
619
|
"shape_is_real_fourier": func_args["return_real_fourier"],
|
@@ -584,14 +630,13 @@ class WedgeReconstructed:
|
|
584
630
|
angles: Tuple[float],
|
585
631
|
opening_axis: int,
|
586
632
|
tilt_axis: int,
|
587
|
-
return_real_fourier: bool,
|
588
633
|
**kwargs: Dict,
|
589
634
|
) -> NDArray:
|
590
635
|
"""
|
591
|
-
Generate a
|
636
|
+
Generate a continous wedge mask with DC component at the center.
|
592
637
|
|
593
|
-
Parameters
|
594
|
-
|
638
|
+
Parameters
|
639
|
+
----------
|
595
640
|
shape : tuple of int
|
596
641
|
The shape of the reconstruction volume.
|
597
642
|
angles : tuple of float
|
@@ -600,27 +645,28 @@ class WedgeReconstructed:
|
|
600
645
|
The axis around which the wedge is opened.
|
601
646
|
tilt_axis : int
|
602
647
|
The axis along which the tilt is applied.
|
603
|
-
return_real_fourier : bool
|
604
|
-
Whether to return the real part of the Fourier transform.
|
605
648
|
|
606
|
-
Returns
|
607
|
-
|
649
|
+
Returns
|
650
|
+
-------
|
608
651
|
NDArray
|
609
|
-
|
652
|
+
Wedge mask.
|
610
653
|
"""
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
654
|
+
start_radians = np.tan(np.radians(90 - angles[0]))
|
655
|
+
stop_radians = np.tan(np.radians(-1 * (90 - angles[1])))
|
656
|
+
|
657
|
+
grid = centered_grid(shape)
|
658
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
659
|
+
ratios = np.where(
|
660
|
+
grid[opening_axis] == 0,
|
661
|
+
np.tan(np.radians(90)) + 1,
|
662
|
+
grid[tilt_axis] / grid[opening_axis],
|
663
|
+
)
|
664
|
+
|
665
|
+
wedge = np.logical_or(start_radians <= ratios, stop_radians >= ratios).astype(
|
666
|
+
np.float32
|
621
667
|
)
|
622
668
|
|
623
|
-
return
|
669
|
+
return wedge
|
624
670
|
|
625
671
|
@staticmethod
|
626
672
|
def step_wedge(
|
@@ -628,15 +674,15 @@ class WedgeReconstructed:
|
|
628
674
|
angles: Tuple[float],
|
629
675
|
opening_axis: int,
|
630
676
|
tilt_axis: int,
|
631
|
-
|
632
|
-
|
677
|
+
weights: Tuple[float] = None,
|
678
|
+
reconstruction_filter : str = None,
|
633
679
|
**kwargs: Dict,
|
634
680
|
) -> NDArray:
|
635
681
|
"""
|
636
|
-
Generate a
|
682
|
+
Generate a per-angle wedge shape with DC component at the center.
|
637
683
|
|
638
|
-
Parameters
|
639
|
-
|
684
|
+
Parameters
|
685
|
+
----------
|
640
686
|
shape : tuple of int
|
641
687
|
The shape of the reconstruction volume.
|
642
688
|
angles : tuple of float
|
@@ -645,35 +691,76 @@ class WedgeReconstructed:
|
|
645
691
|
The axis around which the wedge is opened.
|
646
692
|
tilt_axis : int
|
647
693
|
The axis along which the tilt is applied.
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
694
|
+
reconstruction_filter : str
|
695
|
+
Filter used during reconstruction.
|
696
|
+
weights : tuple of float, optional
|
697
|
+
Weights to assign to individual tilts. Defaults to 1.
|
652
698
|
|
653
|
-
Returns
|
654
|
-
|
699
|
+
Returns
|
700
|
+
-------
|
655
701
|
NDArray
|
656
|
-
|
702
|
+
Wege mask.
|
657
703
|
"""
|
658
|
-
|
704
|
+
from ..backends import NumpyFFTWBackend
|
659
705
|
|
660
706
|
angles = np.asarray(be.to_numpy_array(angles))
|
661
|
-
|
662
|
-
if
|
663
|
-
weights = np.
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
opening_axis
|
672
|
-
|
673
|
-
omit_negative_frequencies=return_real_fourier,
|
707
|
+
|
708
|
+
if weights is None:
|
709
|
+
weights = np.ones(angles.size)
|
710
|
+
weights = np.asarray(weights)
|
711
|
+
|
712
|
+
shape = tuple(int(x) for x in shape)
|
713
|
+
opening_axis, tilt_axis = int(opening_axis), int(tilt_axis)
|
714
|
+
|
715
|
+
weights = np.repeat(weights, angles.size // weights.size)
|
716
|
+
plane = np.zeros(
|
717
|
+
(shape[opening_axis], shape[tilt_axis] + (1 - shape[tilt_axis] % 2)),
|
718
|
+
dtype=np.float32
|
674
719
|
)
|
675
720
|
|
676
|
-
|
721
|
+
# plane = np.zeros((shape[opening_axis], int(2 * np.max(shape)) + 1), dtype=np.float32)
|
722
|
+
|
723
|
+
rec_filter = 1
|
724
|
+
if reconstruction_filter is not None:
|
725
|
+
rec_filter = create_reconstruction_filter(
|
726
|
+
plane.shape[::-1], filter_type = reconstruction_filter, tilt_angles = angles
|
727
|
+
).T
|
728
|
+
|
729
|
+
subset = tuple(
|
730
|
+
slice(None) if i != 0 else slice(x // 2, x // 2 + 1)
|
731
|
+
for i, x in enumerate(plane.shape)
|
732
|
+
)
|
733
|
+
plane_rotated, wedge_volume = np.zeros_like(plane), np.zeros_like(plane)
|
734
|
+
for index in range(angles.shape[0]):
|
735
|
+
plane_rotated.fill(0)
|
736
|
+
plane[subset] = 1
|
737
|
+
rotation_matrix = euler_to_rotationmatrix((angles[index], 0))
|
738
|
+
rotation_matrix = rotation_matrix[np.ix_((0, 1), (0, 1))]
|
739
|
+
|
740
|
+
NumpyFFTWBackend().rigid_transform(
|
741
|
+
arr=plane * rec_filter,
|
742
|
+
rotation_matrix=rotation_matrix,
|
743
|
+
out=plane_rotated,
|
744
|
+
use_geometric_center=True,
|
745
|
+
order=1,
|
746
|
+
)
|
747
|
+
wedge_volume += plane_rotated * weights[index]
|
748
|
+
|
749
|
+
wedge_volume = centered(wedge_volume, (shape[opening_axis], shape[tilt_axis]))
|
750
|
+
np.fmin(wedge_volume, np.max(weights), wedge_volume)
|
751
|
+
|
752
|
+
if opening_axis > tilt_axis:
|
753
|
+
wedge_volume = np.moveaxis(wedge_volume, 1, 0)
|
754
|
+
|
755
|
+
reshape_dimensions = tuple(
|
756
|
+
x if i in (opening_axis, tilt_axis) else 1 for i, x in enumerate(shape)
|
757
|
+
)
|
758
|
+
|
759
|
+
wedge_volume = wedge_volume.reshape(reshape_dimensions)
|
760
|
+
tile_dimensions = np.divide(shape, reshape_dimensions).astype(int)
|
761
|
+
wedge_volume = np.tile(wedge_volume, tile_dimensions)
|
762
|
+
|
763
|
+
return wedge_volume
|
677
764
|
|
678
765
|
|
679
766
|
@dataclass
|
@@ -723,8 +810,8 @@ class CTF:
|
|
723
810
|
"""
|
724
811
|
Initialize :py:class:`CTF` from file.
|
725
812
|
|
726
|
-
Parameters
|
727
|
-
|
813
|
+
Parameters
|
814
|
+
----------
|
728
815
|
filename : str
|
729
816
|
The path to a file with ctf parameters. Supports the following formats:
|
730
817
|
- CTFFIND4
|
@@ -790,16 +877,6 @@ class CTF:
|
|
790
877
|
def __post_init__(self):
|
791
878
|
self.defocus_angle = np.radians(self.defocus_angle)
|
792
879
|
|
793
|
-
kwargs = {
|
794
|
-
"defocus_x": self.defocus_x,
|
795
|
-
"defocus_y": self.defocus_y,
|
796
|
-
"spherical_aberration": self.spherical_aberration,
|
797
|
-
}
|
798
|
-
kwargs = {k: v for k, v in kwargs.items() if v is not None}
|
799
|
-
self._update_parameters(
|
800
|
-
electron_wavelength=self._compute_electron_wavelength(), **kwargs
|
801
|
-
)
|
802
|
-
|
803
880
|
def _compute_electron_wavelength(self, acceleration_voltage: int = None):
|
804
881
|
"""Computes the wavelength of an electron in angstrom."""
|
805
882
|
|
@@ -818,28 +895,10 @@ class CTF:
|
|
818
895
|
electron_wavelength = np.divide(
|
819
896
|
planck_constant * light_velocity, np.sqrt(denominator)
|
820
897
|
)
|
898
|
+
# Convert to Ångstrom
|
821
899
|
electron_wavelength *= 1e10
|
822
900
|
return electron_wavelength
|
823
901
|
|
824
|
-
def _update_parameters(self, **kwargs):
|
825
|
-
"""Update multiple parameters of the CTF instance."""
|
826
|
-
voxel_based = [
|
827
|
-
"electron_wavelength",
|
828
|
-
"spherical_aberration",
|
829
|
-
"defocus_x",
|
830
|
-
"defocus_y",
|
831
|
-
]
|
832
|
-
if "sampling_rate" in kwargs:
|
833
|
-
self.sampling_rate = kwargs["sampling_rate"]
|
834
|
-
|
835
|
-
if "acceleration_voltage" in kwargs:
|
836
|
-
kwargs["electron_wavelength"] = self._compute_electron_wavelength()
|
837
|
-
|
838
|
-
for key, value in kwargs.items():
|
839
|
-
if key in voxel_based and value is not None:
|
840
|
-
value = np.divide(value, np.max(self.sampling_rate))
|
841
|
-
setattr(self, key, value)
|
842
|
-
|
843
902
|
def __call__(self, **kwargs) -> NDArray:
|
844
903
|
func_args = vars(self).copy()
|
845
904
|
func_args.update(kwargs)
|
@@ -865,7 +924,6 @@ class CTF:
|
|
865
924
|
shape: Tuple[int],
|
866
925
|
defocus_x: Tuple[float],
|
867
926
|
angles: Tuple[float],
|
868
|
-
electron_wavelength: float = None,
|
869
927
|
opening_axis: int = None,
|
870
928
|
tilt_axis: int = None,
|
871
929
|
amplitude_contrast: float = 0.07,
|
@@ -883,16 +941,14 @@ class CTF:
|
|
883
941
|
"""
|
884
942
|
Compute the CTF weight tilt stack.
|
885
943
|
|
886
|
-
Parameters
|
887
|
-
|
944
|
+
Parameters
|
945
|
+
----------
|
888
946
|
shape : tuple of int
|
889
947
|
The shape of the CTF.
|
890
948
|
defocus_x : tuple of float
|
891
949
|
The defocus value in x direction.
|
892
950
|
angles : tuple of float
|
893
951
|
The tilt angles.
|
894
|
-
electron_wavelength : float, optional
|
895
|
-
The electron wavelength, defaults to None.
|
896
952
|
opening_axis : int, optional
|
897
953
|
The axis around which the wedge is opened, defaults to None.
|
898
954
|
tilt_axis : int, optional
|
@@ -918,8 +974,8 @@ class CTF:
|
|
918
974
|
**kwargs : Dict
|
919
975
|
Additional keyword arguments.
|
920
976
|
|
921
|
-
Returns
|
922
|
-
|
977
|
+
Returns
|
978
|
+
-------
|
923
979
|
NDArray
|
924
980
|
A stack containing the CTF weight.
|
925
981
|
"""
|
@@ -939,9 +995,13 @@ class CTF:
|
|
939
995
|
correct_defocus_gradient &= tilt_axis is not None
|
940
996
|
correct_defocus_gradient &= opening_axis is not None
|
941
997
|
|
998
|
+
spherical_aberration /= sampling_rate
|
999
|
+
electron_wavelength = self._compute_electron_wavelength() / sampling_rate
|
942
1000
|
for index, angle in enumerate(angles):
|
943
1001
|
defocus_x, defocus_y = defoci_x[index], defoci_y[index]
|
944
1002
|
|
1003
|
+
defocus_x = defocus_x / sampling_rate if defocus_x is not None else None
|
1004
|
+
defocus_y = defocus_y / sampling_rate if defocus_y is not None else None
|
945
1005
|
if correct_defocus_gradient or defocus_y is not None:
|
946
1006
|
grid = fftfreqn(
|
947
1007
|
shape=shape,
|
@@ -978,6 +1038,7 @@ class CTF:
|
|
978
1038
|
angle=angle,
|
979
1039
|
sampling_rate=1,
|
980
1040
|
)
|
1041
|
+
frequency_mask = frequency_grid < 0.5
|
981
1042
|
np.square(frequency_grid, out=frequency_grid)
|
982
1043
|
|
983
1044
|
electron_aberration = spherical_aberration * electron_wavelength**2
|
@@ -993,6 +1054,7 @@ class CTF:
|
|
993
1054
|
)
|
994
1055
|
)
|
995
1056
|
np.sin(-chi, out=chi)
|
1057
|
+
np.multiply(chi, frequency_mask, out=chi)
|
996
1058
|
stack[index] = chi
|
997
1059
|
|
998
1060
|
# Avoid contrast inversion
|