pytme 0.2.9__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.2.9.data/scripts/estimate_ram_usage.py +97 -0
- pytme-0.2.9.data/scripts/match_template.py +1135 -0
- pytme-0.2.9.data/scripts/postprocess.py +622 -0
- pytme-0.2.9.data/scripts/preprocess.py +209 -0
- pytme-0.2.9.data/scripts/preprocessor_gui.py +1227 -0
- pytme-0.2.9.dist-info/METADATA +95 -0
- pytme-0.2.9.dist-info/RECORD +119 -0
- pytme-0.2.9.dist-info/WHEEL +5 -0
- pytme-0.2.9.dist-info/entry_points.txt +6 -0
- pytme-0.2.9.dist-info/licenses/LICENSE +153 -0
- pytme-0.2.9.dist-info/top_level.txt +3 -0
- scripts/__init__.py +0 -0
- scripts/estimate_ram_usage.py +97 -0
- scripts/match_template.py +1135 -0
- scripts/postprocess.py +622 -0
- scripts/preprocess.py +209 -0
- scripts/preprocessor_gui.py +1227 -0
- tests/__init__.py +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/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 +216 -0
- tests/test_backends.py +446 -0
- tests/test_density.py +503 -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 +124 -0
- tests/test_matching_memory.py +30 -0
- tests/test_matching_optimization.py +226 -0
- tests/test_matching_utils.py +189 -0
- tests/test_orientations.py +175 -0
- tests/test_parser.py +33 -0
- tests/test_rotations.py +153 -0
- tests/test_structure.py +247 -0
- tme/__init__.py +6 -0
- tme/__version__.py +1 -0
- tme/analyzer/__init__.py +2 -0
- tme/analyzer/_utils.py +186 -0
- tme/analyzer/aggregation.py +577 -0
- tme/analyzer/peaks.py +953 -0
- tme/backends/__init__.py +171 -0
- tme/backends/_cupy_utils.py +734 -0
- tme/backends/_jax_utils.py +188 -0
- tme/backends/cupy_backend.py +294 -0
- tme/backends/jax_backend.py +314 -0
- tme/backends/matching_backend.py +1270 -0
- tme/backends/mlx_backend.py +241 -0
- tme/backends/npfftw_backend.py +583 -0
- tme/backends/pytorch_backend.py +430 -0
- tme/data/__init__.py +0 -0
- tme/data/c48n309.npy +0 -0
- tme/data/c48n527.npy +0 -0
- tme/data/c48n9.npy +0 -0
- tme/data/c48u1.npy +0 -0
- tme/data/c48u1153.npy +0 -0
- tme/data/c48u1201.npy +0 -0
- tme/data/c48u1641.npy +0 -0
- tme/data/c48u181.npy +0 -0
- tme/data/c48u2219.npy +0 -0
- tme/data/c48u27.npy +0 -0
- tme/data/c48u2947.npy +0 -0
- tme/data/c48u3733.npy +0 -0
- tme/data/c48u4749.npy +0 -0
- tme/data/c48u5879.npy +0 -0
- tme/data/c48u7111.npy +0 -0
- tme/data/c48u815.npy +0 -0
- tme/data/c48u83.npy +0 -0
- tme/data/c48u8649.npy +0 -0
- tme/data/c600v.npy +0 -0
- tme/data/c600vc.npy +0 -0
- tme/data/metadata.yaml +80 -0
- tme/data/quat_to_numpy.py +42 -0
- tme/data/scattering_factors.pickle +0 -0
- tme/density.py +2263 -0
- tme/extensions.cpython-311-darwin.so +0 -0
- tme/external/bindings.cpp +332 -0
- tme/filters/__init__.py +6 -0
- tme/filters/_utils.py +311 -0
- tme/filters/bandpass.py +230 -0
- tme/filters/compose.py +81 -0
- tme/filters/ctf.py +393 -0
- tme/filters/reconstruction.py +160 -0
- tme/filters/wedge.py +542 -0
- tme/filters/whitening.py +191 -0
- tme/matching_data.py +863 -0
- tme/matching_exhaustive.py +497 -0
- tme/matching_optimization.py +1311 -0
- tme/matching_scores.py +1183 -0
- tme/matching_utils.py +1188 -0
- tme/memory.py +337 -0
- tme/orientations.py +598 -0
- tme/parser.py +685 -0
- tme/preprocessor.py +1329 -0
- tme/rotations.py +350 -0
- tme/structure.py +1864 -0
- tme/types.py +13 -0
tme/rotations.py
ADDED
@@ -0,0 +1,350 @@
|
|
1
|
+
""" Implements various means of generating rotation matrices.
|
2
|
+
|
3
|
+
Copyright (c) 2023-2025 European Molecular Biology Laboratory
|
4
|
+
|
5
|
+
Author: Valentin Maurer <valentin.maurer@embl-hamburg.de>
|
6
|
+
"""
|
7
|
+
|
8
|
+
import yaml
|
9
|
+
import warnings
|
10
|
+
from typing import Tuple
|
11
|
+
from os.path import join, dirname
|
12
|
+
|
13
|
+
import numpy as np
|
14
|
+
from scipy.spatial.transform import Rotation
|
15
|
+
|
16
|
+
from .types import NDArray
|
17
|
+
|
18
|
+
__all__ = [
|
19
|
+
"get_cone_rotations",
|
20
|
+
"align_vectors",
|
21
|
+
"euler_to_rotationmatrix",
|
22
|
+
"euler_from_rotationmatrix",
|
23
|
+
"get_rotation_matrices",
|
24
|
+
"align_to_axis",
|
25
|
+
]
|
26
|
+
|
27
|
+
|
28
|
+
def _sample_cone(
|
29
|
+
angle: float, sampling: float, axis: Tuple[float] = (1, 0, 0)
|
30
|
+
) -> NDArray:
|
31
|
+
"""
|
32
|
+
Sample points on a cone surface around cone_axis using golden spiral distribution.
|
33
|
+
|
34
|
+
Parameters
|
35
|
+
----------
|
36
|
+
angle : float
|
37
|
+
The half-angle of the cone in degrees.
|
38
|
+
sampling : float
|
39
|
+
Angular increment used for sampling points in degrees.
|
40
|
+
axis : tuple of floats
|
41
|
+
Vector to align the cone with.
|
42
|
+
|
43
|
+
Returns
|
44
|
+
-------
|
45
|
+
NDArray
|
46
|
+
Array of points around axis with shape n,3.
|
47
|
+
|
48
|
+
References
|
49
|
+
----------
|
50
|
+
.. [1] https://stackoverflow.com/questions/9600801/evenly-distributing-n-points-on-a-sphere
|
51
|
+
"""
|
52
|
+
theta = np.linspace(0, angle, round(angle / sampling) + 1)
|
53
|
+
number_of_points = np.ceil(
|
54
|
+
360 * np.divide(np.sin(np.radians(theta)), sampling),
|
55
|
+
)
|
56
|
+
number_of_points = int(np.sum(number_of_points + 1) + 2)
|
57
|
+
|
58
|
+
indices = np.arange(0, number_of_points, dtype=float) + 0.5
|
59
|
+
radius = np.radians(angle * np.sqrt(indices / number_of_points))
|
60
|
+
theta = np.pi * (1 + np.sqrt(5)) * indices
|
61
|
+
|
62
|
+
points = np.stack(
|
63
|
+
[
|
64
|
+
np.cos(radius),
|
65
|
+
np.cos(theta) * np.sin(radius),
|
66
|
+
np.sin(theta) * np.sin(radius),
|
67
|
+
],
|
68
|
+
axis=1,
|
69
|
+
)
|
70
|
+
|
71
|
+
rotation = Rotation.from_euler(
|
72
|
+
angles=align_vectors((1, 0, 0), axis, seq="zyz"),
|
73
|
+
seq="zyz",
|
74
|
+
degrees=True,
|
75
|
+
)
|
76
|
+
return rotation.apply(points)
|
77
|
+
|
78
|
+
|
79
|
+
def get_cone_rotations(
|
80
|
+
cone_angle: float,
|
81
|
+
cone_sampling: float,
|
82
|
+
axis: Tuple[float] = (1, 0, 0),
|
83
|
+
axis_angle: float = 360.0,
|
84
|
+
axis_sampling: float = None,
|
85
|
+
reference: Tuple[float] = (1, 0, 0),
|
86
|
+
n_symmetry: int = 1,
|
87
|
+
seq: str = None,
|
88
|
+
) -> NDArray:
|
89
|
+
"""
|
90
|
+
Generate rotations describing the possible placements of a vector in a cone.
|
91
|
+
|
92
|
+
Parameters
|
93
|
+
----------
|
94
|
+
cone_angle : float
|
95
|
+
The half-angle of the cone in degrees.
|
96
|
+
cone_sampling : float
|
97
|
+
Angular increment used for sampling points on the cone in degrees.
|
98
|
+
axis : Tuple[float], optional
|
99
|
+
Base-vector of the cone.
|
100
|
+
axis_angle : float, optional
|
101
|
+
The total angle of rotation around the cone axis in degrees (default is 360.0).
|
102
|
+
axis_sampling : float, optional
|
103
|
+
Angular increment used for sampling points around the cone axis in degrees.
|
104
|
+
If None, it takes the value of cone_sampling.
|
105
|
+
reference : Tuple[float], optional
|
106
|
+
Returned rotations will map this point onto the cone. In practice, this is
|
107
|
+
the principal axis of the template.
|
108
|
+
n_symmetry : int, optional
|
109
|
+
Number of symmetry axis around the vector axis.
|
110
|
+
seq : str, optional
|
111
|
+
Convention for angles. By default returns rotation matrices.
|
112
|
+
|
113
|
+
Returns
|
114
|
+
-------
|
115
|
+
NDArray
|
116
|
+
An arary of rotations represented as stack of rotation matrices when convention
|
117
|
+
None (n, 3, 3) or an array of Euler angles (n, 3) for available conventions.
|
118
|
+
"""
|
119
|
+
if axis_sampling is None:
|
120
|
+
axis_sampling = cone_sampling
|
121
|
+
|
122
|
+
points = _sample_cone(angle=cone_angle, sampling=cone_sampling, axis=axis)
|
123
|
+
reference = np.asarray(reference).astype(np.float32)
|
124
|
+
reference /= np.linalg.norm(reference)
|
125
|
+
|
126
|
+
axis_angle /= n_symmetry
|
127
|
+
phi_steps = np.maximum(np.round(axis_angle / axis_sampling), 1).astype(int)
|
128
|
+
phi = np.linspace(0, axis_angle, phi_steps + 1)[:-1]
|
129
|
+
|
130
|
+
axis_rotation = Rotation.from_rotvec(axis * np.radians(phi)[:, None])
|
131
|
+
all_rotations = [
|
132
|
+
axis_rotation * Rotation.from_matrix(align_vectors(reference, x))
|
133
|
+
for x in points
|
134
|
+
]
|
135
|
+
|
136
|
+
rotations = Rotation.concatenate(all_rotations)
|
137
|
+
if seq is None:
|
138
|
+
return rotations.as_matrix()
|
139
|
+
|
140
|
+
return rotations.as_euler(seq=seq, degrees=True)
|
141
|
+
|
142
|
+
|
143
|
+
def align_vectors(base: NDArray, target: NDArray = (0, 0, 1), seq: str = None):
|
144
|
+
"""
|
145
|
+
Compute the rotation matrix or Euler angles required to align an initial vector with a target vector.
|
146
|
+
|
147
|
+
Parameters
|
148
|
+
----------
|
149
|
+
base : NDArray
|
150
|
+
The basis vector.
|
151
|
+
target : NDArray, optional
|
152
|
+
The vector to map base to, defaults to (0,0,1).
|
153
|
+
seq : str, optional
|
154
|
+
Euler angle convention, None returns a rotation matrix instead.
|
155
|
+
|
156
|
+
Returns
|
157
|
+
-------
|
158
|
+
NDArray
|
159
|
+
Rotation matrix if seq is None otherwise Euler angles in desired convention
|
160
|
+
"""
|
161
|
+
base = np.asarray(base, dtype=np.float32)
|
162
|
+
target = np.asarray(target, dtype=np.float32)
|
163
|
+
|
164
|
+
rotation, error = Rotation.align_vectors(target, base)
|
165
|
+
if seq is None:
|
166
|
+
return rotation.as_matrix()
|
167
|
+
return rotation.as_euler(seq=seq, degrees=True)
|
168
|
+
|
169
|
+
|
170
|
+
def euler_to_rotationmatrix(angles: Tuple[float], seq: str = "zyz") -> NDArray:
|
171
|
+
"""
|
172
|
+
Convert Euler angles to a rotation matrix.
|
173
|
+
|
174
|
+
Parameters
|
175
|
+
----------
|
176
|
+
angles : tuple
|
177
|
+
A tuple representing the Euler angles in degrees.
|
178
|
+
seq : str, optional
|
179
|
+
Euler angle convention.
|
180
|
+
|
181
|
+
Returns
|
182
|
+
-------
|
183
|
+
NDArray
|
184
|
+
The generated rotation matrix.
|
185
|
+
"""
|
186
|
+
n_angles = len(angles)
|
187
|
+
angle_convention = seq[:n_angles]
|
188
|
+
if n_angles == 1:
|
189
|
+
angles = (angles, 0, 0)
|
190
|
+
rotation_matrix = Rotation.from_euler(
|
191
|
+
seq=angle_convention, angles=angles, degrees=True
|
192
|
+
)
|
193
|
+
return rotation_matrix.as_matrix().astype(np.float32)
|
194
|
+
|
195
|
+
|
196
|
+
def euler_from_rotationmatrix(rotation_matrix: NDArray, seq: str = "zyz") -> Tuple:
|
197
|
+
"""
|
198
|
+
Convert a rotation matrix to euler angles.
|
199
|
+
|
200
|
+
Parameters
|
201
|
+
----------
|
202
|
+
rotation_matrix : NDArray
|
203
|
+
A 2 x 2 or 3 x 3 rotation matrix.
|
204
|
+
seq : str, optional
|
205
|
+
Euler angle convention, zyz by default.
|
206
|
+
|
207
|
+
Returns
|
208
|
+
-------
|
209
|
+
Tuple
|
210
|
+
The generate euler angles in degrees
|
211
|
+
"""
|
212
|
+
if rotation_matrix.shape[0] == 2:
|
213
|
+
temp_matrix = np.eye(3)
|
214
|
+
temp_matrix[:2, :2] = rotation_matrix
|
215
|
+
rotation_matrix = temp_matrix
|
216
|
+
|
217
|
+
with warnings.catch_warnings():
|
218
|
+
warnings.simplefilter("ignore")
|
219
|
+
rotation = Rotation.from_matrix(rotation_matrix)
|
220
|
+
angles = rotation.as_euler(seq=seq, degrees=True).astype(np.float32)
|
221
|
+
return angles
|
222
|
+
|
223
|
+
|
224
|
+
def get_rotation_matrices(
|
225
|
+
angular_sampling: float, dim: int = 3, use_optimized_set: bool = True
|
226
|
+
) -> NDArray:
|
227
|
+
"""
|
228
|
+
Returns rotation matrices with desired ``angular_sampling`` rate.
|
229
|
+
|
230
|
+
Parameters
|
231
|
+
----------
|
232
|
+
angular_sampling : float
|
233
|
+
The desired angular sampling in degrees.
|
234
|
+
dim : int, optional
|
235
|
+
Dimension of the rotation matrices.
|
236
|
+
use_optimized_set : bool, optional
|
237
|
+
Use optimized rotational sets, True by default and available for dim=3.
|
238
|
+
|
239
|
+
Notes
|
240
|
+
-----
|
241
|
+
For dim = 3 optimized sets are used, otherwise QR-decomposition.
|
242
|
+
|
243
|
+
Returns
|
244
|
+
-------
|
245
|
+
NDArray
|
246
|
+
Array of shape (n, d, d) containing n rotation matrices.
|
247
|
+
"""
|
248
|
+
if dim == 3 and use_optimized_set:
|
249
|
+
quaternions, *_ = _load_quaternions_by_angle(angular_sampling)
|
250
|
+
ret = Rotation.from_quat(quaternions).as_matrix()
|
251
|
+
else:
|
252
|
+
num_rotations = dim * (dim - 1) // 2
|
253
|
+
k = int((360 / angular_sampling) ** num_rotations)
|
254
|
+
As = np.random.randn(k, dim, dim)
|
255
|
+
ret, _ = np.linalg.qr(As)
|
256
|
+
dets = np.linalg.det(ret)
|
257
|
+
neg_dets = dets < 0
|
258
|
+
ret[neg_dets, :, -1] *= -1
|
259
|
+
ret[0] = np.eye(dim, dtype=ret.dtype)
|
260
|
+
return ret
|
261
|
+
|
262
|
+
|
263
|
+
def _load_quaternions_by_angle(
|
264
|
+
angular_sampling: float,
|
265
|
+
) -> Tuple[NDArray, NDArray, float]:
|
266
|
+
"""
|
267
|
+
Get orientations and weights proportional to the given angular_sampling.
|
268
|
+
|
269
|
+
Parameters
|
270
|
+
----------
|
271
|
+
angular_sampling : float
|
272
|
+
Requested angular sampling.
|
273
|
+
|
274
|
+
Returns
|
275
|
+
-------
|
276
|
+
Tuple[NDArray, NDArray, float]
|
277
|
+
Quaternions, associated weights and realized angular sampling rate.
|
278
|
+
"""
|
279
|
+
# Metadata contains (N orientations, rotational sampling, coverage as values)
|
280
|
+
with open(join(dirname(__file__), "data", "metadata.yaml"), "r") as infile:
|
281
|
+
metadata = yaml.full_load(infile)
|
282
|
+
|
283
|
+
set_diffs = {
|
284
|
+
setname: abs(angular_sampling - set_angle)
|
285
|
+
for setname, (_, set_angle, _) in metadata.items()
|
286
|
+
}
|
287
|
+
fname = min(set_diffs, key=set_diffs.get)
|
288
|
+
|
289
|
+
infile = join(dirname(__file__), "data", fname)
|
290
|
+
quat_weights = np.load(infile)
|
291
|
+
|
292
|
+
quat = quat_weights[:, :4]
|
293
|
+
weights = quat_weights[:, -1]
|
294
|
+
angle = metadata[fname][0]
|
295
|
+
|
296
|
+
return quat, weights, angle
|
297
|
+
|
298
|
+
|
299
|
+
def align_to_axis(
|
300
|
+
coordinates: NDArray,
|
301
|
+
weights: NDArray = None,
|
302
|
+
axis: int = 2,
|
303
|
+
flip: bool = False,
|
304
|
+
eigenvector_index: int = 0,
|
305
|
+
) -> NDArray:
|
306
|
+
"""
|
307
|
+
Calculate a rotation matrix that aligns the principal axis of a point cloud
|
308
|
+
with a specified coordinate axis.
|
309
|
+
|
310
|
+
Parameters
|
311
|
+
----------
|
312
|
+
coordinates : NDArray
|
313
|
+
Array of 3D coordinates with shape (n, 3) representing the point cloud.
|
314
|
+
weights : NDArray
|
315
|
+
Coordinate weighting factors with shape (n,).
|
316
|
+
axis : int, optional
|
317
|
+
The target axis to align with, defaults to 2 (z-axis).
|
318
|
+
flip : bool, optional
|
319
|
+
Whether to align with the negative direction of the axis, default is False.
|
320
|
+
eigenvector_index : int, optional
|
321
|
+
Index of eigenvector to select, sorted by descending eigenvalues.
|
322
|
+
0 = largest eigenvalue (most variance), 1 = second largest, etc.
|
323
|
+
Default is 0 (primary principal component).
|
324
|
+
|
325
|
+
Returns
|
326
|
+
-------
|
327
|
+
NDArray
|
328
|
+
3x3 rotation matrix that aligns the principal component of the
|
329
|
+
coordinates with the specified axis.
|
330
|
+
"""
|
331
|
+
axis = int(axis)
|
332
|
+
coordinates = np.asarray(coordinates)
|
333
|
+
alignment_axis = np.array(
|
334
|
+
[0 if i != axis else 1 for i in range(coordinates.shape[1])]
|
335
|
+
)
|
336
|
+
if flip:
|
337
|
+
alignment_axis *= -1
|
338
|
+
|
339
|
+
ndim = coordinates.shape[1]
|
340
|
+
if eigenvector_index >= ndim:
|
341
|
+
raise ValueError(f"eigenvector_index has to be less than {ndim}.")
|
342
|
+
|
343
|
+
avg = np.average(coordinates, axis=0, weights=weights)
|
344
|
+
coordinates = coordinates - avg
|
345
|
+
cov_matrix = np.cov(coordinates.T, aweights=weights)
|
346
|
+
|
347
|
+
# Eigenvalues are already sorted in ascending order
|
348
|
+
eigenvalues, eigenvectors = np.linalg.eigh(cov_matrix)
|
349
|
+
eigenvector = eigenvectors[:, -(eigenvector_index + 1)]
|
350
|
+
return align_vectors(eigenvector, alignment_axis)
|