pytme 0.1.8__cp311-cp311-macosx_14_0_arm64.whl → 0.2.0b0__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.1.8.data → pytme-0.2.0b0.data}/scripts/match_template.py +148 -126
- pytme-0.2.0b0.data/scripts/postprocess.py +570 -0
- {pytme-0.1.8.data → pytme-0.2.0b0.data}/scripts/preprocessor_gui.py +244 -60
- {pytme-0.1.8.dist-info → pytme-0.2.0b0.dist-info}/METADATA +3 -1
- pytme-0.2.0b0.dist-info/RECORD +66 -0
- {pytme-0.1.8.dist-info → pytme-0.2.0b0.dist-info}/WHEEL +1 -1
- scripts/extract_candidates.py +218 -0
- scripts/match_template.py +148 -126
- scripts/match_template_filters.py +852 -0
- scripts/postprocess.py +380 -435
- scripts/preprocessor_gui.py +244 -60
- scripts/refine_matches.py +218 -0
- tme/__init__.py +2 -1
- tme/__version__.py +1 -1
- tme/analyzer.py +545 -78
- tme/backends/cupy_backend.py +80 -15
- tme/backends/npfftw_backend.py +33 -2
- tme/backends/pytorch_backend.py +15 -7
- tme/density.py +156 -63
- tme/extensions.cpython-311-darwin.so +0 -0
- tme/matching_constrained.py +195 -0
- tme/matching_data.py +76 -32
- tme/matching_exhaustive.py +366 -204
- tme/matching_memory.py +1 -0
- tme/matching_optimization.py +728 -651
- tme/matching_utils.py +152 -8
- tme/orientations.py +561 -0
- tme/preprocessor.py +21 -18
- tme/structure.py +2 -37
- pytme-0.1.8.data/scripts/postprocess.py +0 -625
- pytme-0.1.8.dist-info/RECORD +0 -61
- {pytme-0.1.8.data → pytme-0.2.0b0.data}/scripts/estimate_ram_usage.py +0 -0
- {pytme-0.1.8.data → pytme-0.2.0b0.data}/scripts/preprocess.py +0 -0
- {pytme-0.1.8.dist-info → pytme-0.2.0b0.dist-info}/LICENSE +0 -0
- {pytme-0.1.8.dist-info → pytme-0.2.0b0.dist-info}/entry_points.txt +0 -0
- {pytme-0.1.8.dist-info → pytme-0.2.0b0.dist-info}/top_level.txt +0 -0
tme/orientations.py
ADDED
@@ -0,0 +1,561 @@
|
|
1
|
+
#!python3
|
2
|
+
""" Handle template matching peaks and convert between formats.
|
3
|
+
|
4
|
+
Copyright (c) 2024 European Molecular Biology Laboratory
|
5
|
+
|
6
|
+
Author: Valentin Maurer <valentin.maurer@embl-hamburg.de>
|
7
|
+
"""
|
8
|
+
import re
|
9
|
+
from collections import deque
|
10
|
+
from dataclasses import dataclass
|
11
|
+
from typing import List, Tuple, Dict
|
12
|
+
|
13
|
+
import numpy as np
|
14
|
+
from scipy.spatial.transform import Rotation
|
15
|
+
|
16
|
+
|
17
|
+
@dataclass
|
18
|
+
class Orientations:
|
19
|
+
"""
|
20
|
+
Handle template matching peaks and convert between formats.
|
21
|
+
"""
|
22
|
+
|
23
|
+
#: Return a numpy array with translations of each orientation (n x d).
|
24
|
+
translations: np.ndarray
|
25
|
+
|
26
|
+
#: Return a numpy array with euler angles of each orientation in zxy format (n x d).
|
27
|
+
rotations: np.ndarray
|
28
|
+
|
29
|
+
#: Return a numpy array with the score of each orientation (n, ).
|
30
|
+
scores: np.ndarray
|
31
|
+
|
32
|
+
#: Return a numpy array with additional orientation details (n, ).
|
33
|
+
details: np.ndarray
|
34
|
+
|
35
|
+
def __iter__(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
36
|
+
"""
|
37
|
+
Iterate over the current class instance. Each iteration returns a orientation
|
38
|
+
defined by its translation, rotation, score and additional detail.
|
39
|
+
|
40
|
+
Yields
|
41
|
+
------
|
42
|
+
Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]
|
43
|
+
A tuple of arrays defining the given orientation.
|
44
|
+
"""
|
45
|
+
yield from zip(self.translations, self.rotations, self.scores, self.details)
|
46
|
+
|
47
|
+
def __getitem__(self, indices: List[int]) -> "Orientations":
|
48
|
+
"""
|
49
|
+
Retrieve a subset of orientations based on the provided indices.
|
50
|
+
|
51
|
+
Parameters
|
52
|
+
----------
|
53
|
+
indices : List[int]
|
54
|
+
A list of indices specifying the orientations to be retrieved.
|
55
|
+
|
56
|
+
Returns
|
57
|
+
-------
|
58
|
+
:py:class:`Orientations`
|
59
|
+
A new :py:class:`Orientations`instance containing only the selected orientations.
|
60
|
+
"""
|
61
|
+
indices = np.asarray(indices)
|
62
|
+
attributes = (
|
63
|
+
"translations",
|
64
|
+
"rotations",
|
65
|
+
"scores",
|
66
|
+
"details",
|
67
|
+
)
|
68
|
+
kwargs = {attr: getattr(self, attr)[indices] for attr in attributes}
|
69
|
+
return self.__class__(**kwargs)
|
70
|
+
|
71
|
+
def to_file(self, filename: str, file_format: type = None, **kwargs) -> None:
|
72
|
+
"""
|
73
|
+
Save the current class instance to a file in the specified format.
|
74
|
+
|
75
|
+
Parameters
|
76
|
+
----------
|
77
|
+
filename : str
|
78
|
+
The name of the file where the orientations will be saved.
|
79
|
+
file_format : type, optional
|
80
|
+
The format in which to save the orientations. Supported formats are 'text' and 'relion'.
|
81
|
+
**kwargs : dict
|
82
|
+
Additional keyword arguments specific to the file format.
|
83
|
+
|
84
|
+
Raises
|
85
|
+
------
|
86
|
+
ValueError
|
87
|
+
If an unsupported file format is specified.
|
88
|
+
"""
|
89
|
+
mapping = {
|
90
|
+
"text": self._to_text,
|
91
|
+
"relion": self._to_relion_star,
|
92
|
+
"dynamo": self._to_dynamo_tbl,
|
93
|
+
}
|
94
|
+
if file_format is None:
|
95
|
+
file_format = "text"
|
96
|
+
if filename.lower().endswith(".star"):
|
97
|
+
file_format = "relion"
|
98
|
+
elif filename.lower().endswith(".tbl"):
|
99
|
+
file_format = "dynamo"
|
100
|
+
|
101
|
+
func = mapping.get(file_format, None)
|
102
|
+
if func is None:
|
103
|
+
raise ValueError(
|
104
|
+
f"{file_format} not implemented. Supported are {','.join(mapping.keys())}."
|
105
|
+
)
|
106
|
+
|
107
|
+
return func(filename=filename, **kwargs)
|
108
|
+
|
109
|
+
def _to_text(self, filename: str) -> None:
|
110
|
+
"""
|
111
|
+
Save orientations in a text file format.
|
112
|
+
|
113
|
+
Parameters
|
114
|
+
----------
|
115
|
+
filename : str
|
116
|
+
The name of the file to save the orientations.
|
117
|
+
|
118
|
+
Notes
|
119
|
+
-----
|
120
|
+
The file is saved with a header specifying each column: z, y, x, euler_z,
|
121
|
+
euler_y, euler_x, score, detail. Each row in the file corresponds to an orientation.
|
122
|
+
"""
|
123
|
+
header = "\t".join(
|
124
|
+
["z", "y", "x", "euler_z", "euler_y", "euler_x", "score", "detail"]
|
125
|
+
)
|
126
|
+
with open(filename, mode="w", encoding="utf-8") as ofile:
|
127
|
+
_ = ofile.write(f"{header}\n")
|
128
|
+
for translation, angles, score, detail in self:
|
129
|
+
translation_string = "\t".join([str(x) for x in translation])
|
130
|
+
angle_string = "\t".join([str(x) for x in angles])
|
131
|
+
_ = ofile.write(
|
132
|
+
f"{translation_string}\t{angle_string}\t{score}\t{detail}\n"
|
133
|
+
)
|
134
|
+
return None
|
135
|
+
|
136
|
+
def _to_dynamo_tbl(
|
137
|
+
self,
|
138
|
+
filename: str,
|
139
|
+
name_prefix: str = None,
|
140
|
+
sampling_rate: float = 1.0,
|
141
|
+
subtomogram_size: int = 0,
|
142
|
+
) -> None:
|
143
|
+
"""
|
144
|
+
Save orientations in Dynamo's tbl file format.
|
145
|
+
|
146
|
+
Parameters
|
147
|
+
----------
|
148
|
+
filename : str
|
149
|
+
The name of the file to save the orientations.
|
150
|
+
sampling_rate : float, optional
|
151
|
+
Subtomogram sampling rate in angstrom per voxel
|
152
|
+
|
153
|
+
Notes
|
154
|
+
-----
|
155
|
+
The file is saved with a standard header used in Dynamo tbl files
|
156
|
+
outlined in [1]_. Each row corresponds to a particular partice.
|
157
|
+
|
158
|
+
References
|
159
|
+
----------
|
160
|
+
.. [1] https://wiki.dynamo.biozentrum.unibas.ch/w/index.php/Table
|
161
|
+
|
162
|
+
The file is saved with a standard header used in Dynamo STAR files.
|
163
|
+
Each row in the file corresponds to an orientation.
|
164
|
+
"""
|
165
|
+
with open(filename, mode="w", encoding="utf-8") as ofile:
|
166
|
+
for index, (translation, rotation, score, detail) in enumerate(self):
|
167
|
+
rotation = Rotation.from_euler("zyx", rotation, degrees=True)
|
168
|
+
rotation = rotation.as_euler(seq="xyx", degrees=True)
|
169
|
+
out = [
|
170
|
+
index,
|
171
|
+
1,
|
172
|
+
0,
|
173
|
+
0,
|
174
|
+
0,
|
175
|
+
0,
|
176
|
+
*rotation,
|
177
|
+
self.scores[index],
|
178
|
+
self.scores[index],
|
179
|
+
0,
|
180
|
+
0,
|
181
|
+
# Wedge parameters
|
182
|
+
-90,
|
183
|
+
90,
|
184
|
+
-60,
|
185
|
+
60,
|
186
|
+
0,
|
187
|
+
0,
|
188
|
+
0,
|
189
|
+
0,
|
190
|
+
0,
|
191
|
+
0,
|
192
|
+
# Coordinate in original volume
|
193
|
+
*translation[::-1],
|
194
|
+
0,
|
195
|
+
0,
|
196
|
+
0,
|
197
|
+
0,
|
198
|
+
0,
|
199
|
+
0,
|
200
|
+
0,
|
201
|
+
0,
|
202
|
+
sampling_rate,
|
203
|
+
3,
|
204
|
+
0,
|
205
|
+
0,
|
206
|
+
]
|
207
|
+
_ = ofile.write(" ".join([str(x) for x in out]) + "\n")
|
208
|
+
|
209
|
+
return None
|
210
|
+
|
211
|
+
def _to_relion_star(
|
212
|
+
self,
|
213
|
+
filename: str,
|
214
|
+
name_prefix: str = None,
|
215
|
+
ctf_image: str = None,
|
216
|
+
sampling_rate: float = 1.0,
|
217
|
+
subtomogram_size: int = 0,
|
218
|
+
) -> None:
|
219
|
+
"""
|
220
|
+
Save orientations in RELION's STAR file format.
|
221
|
+
|
222
|
+
Parameters
|
223
|
+
----------
|
224
|
+
filename : str
|
225
|
+
The name of the file to save the orientations.
|
226
|
+
name_prefix : str, optional
|
227
|
+
A prefix to add to the image names in the STAR file.
|
228
|
+
ctf_image : str, optional
|
229
|
+
Path to CTF or wedge mask RELION.
|
230
|
+
sampling_rate : float, optional
|
231
|
+
Subtomogram sampling rate in angstrom per voxel
|
232
|
+
subtomogram_size : int, optional
|
233
|
+
Size of the square shaped subtomogram.
|
234
|
+
|
235
|
+
Notes
|
236
|
+
-----
|
237
|
+
The file is saved with a standard header used in RELION STAR files.
|
238
|
+
Each row in the file corresponds to an orientation.
|
239
|
+
"""
|
240
|
+
optics_header = [
|
241
|
+
"# version 30001",
|
242
|
+
"data_optics",
|
243
|
+
"",
|
244
|
+
"loop_",
|
245
|
+
"_rlnOpticsGroup",
|
246
|
+
"_rlnOpticsGroupName",
|
247
|
+
"_rlnSphericalAberration",
|
248
|
+
"_rlnVoltage",
|
249
|
+
"_rlnImageSize",
|
250
|
+
"_rlnImageDimensionality",
|
251
|
+
"_rlnImagePixelSize",
|
252
|
+
]
|
253
|
+
optics_data = [
|
254
|
+
"1",
|
255
|
+
"opticsGroup1",
|
256
|
+
"2.700000",
|
257
|
+
"300.000000",
|
258
|
+
str(int(subtomogram_size)),
|
259
|
+
"3",
|
260
|
+
str(float(sampling_rate)),
|
261
|
+
]
|
262
|
+
optics_header = "\n".join(optics_header)
|
263
|
+
optics_data = "\t".join(optics_data)
|
264
|
+
|
265
|
+
header = [
|
266
|
+
"data_particles",
|
267
|
+
"",
|
268
|
+
"loop_",
|
269
|
+
"_rlnCoordinateX",
|
270
|
+
"_rlnCoordinateY",
|
271
|
+
"_rlnCoordinateZ",
|
272
|
+
"_rlnImageName",
|
273
|
+
"_rlnAngleRot",
|
274
|
+
"_rlnAngleTilt",
|
275
|
+
"_rlnAnglePsi",
|
276
|
+
"_rlnOpticsGroup",
|
277
|
+
]
|
278
|
+
if ctf_image is not None:
|
279
|
+
header.append("_rlnCtfImage")
|
280
|
+
|
281
|
+
ctf_image = "" if ctf_image is None else f"\t{ctf_image}"
|
282
|
+
|
283
|
+
header = "\n".join(header)
|
284
|
+
name_prefix = "" if name_prefix is None else name_prefix
|
285
|
+
|
286
|
+
with open(filename, mode="w", encoding="utf-8") as ofile:
|
287
|
+
_ = ofile.write(f"{optics_header}\n")
|
288
|
+
_ = ofile.write(f"{optics_data}\n")
|
289
|
+
|
290
|
+
_ = ofile.write("\n# version 30001\n")
|
291
|
+
_ = ofile.write(f"{header}\n")
|
292
|
+
|
293
|
+
# pyTME uses a zyx data layout
|
294
|
+
for index, (translation, rotation, score, detail) in enumerate(self):
|
295
|
+
rotation = Rotation.from_euler("zyx", rotation, degrees=True)
|
296
|
+
rotation = rotation.as_euler(seq="xyx", degrees=True)
|
297
|
+
|
298
|
+
translation_string = "\t".join([str(x) for x in translation][::-1])
|
299
|
+
angle_string = "\t".join([str(x) for x in rotation])
|
300
|
+
name = f"{name_prefix}_{index}.mrc"
|
301
|
+
_ = ofile.write(
|
302
|
+
f"{translation_string}\t{name}\t{angle_string}\t1{ctf_image}\n"
|
303
|
+
)
|
304
|
+
|
305
|
+
return None
|
306
|
+
|
307
|
+
@classmethod
|
308
|
+
def from_file(
|
309
|
+
cls, filename: str, file_format: type = None, **kwargs
|
310
|
+
) -> "Orientations":
|
311
|
+
"""
|
312
|
+
Create an instance of :py:class:`Orientations` from a file.
|
313
|
+
|
314
|
+
Parameters
|
315
|
+
----------
|
316
|
+
filename : str
|
317
|
+
The name of the file from which to read the orientations.
|
318
|
+
file_format : type, optional
|
319
|
+
The format of the file. Currently, only 'text' format is supported.
|
320
|
+
**kwargs : dict
|
321
|
+
Additional keyword arguments specific to the file format.
|
322
|
+
|
323
|
+
Returns
|
324
|
+
-------
|
325
|
+
:py:class:`Orientations`
|
326
|
+
An instance of :py:class:`Orientations` populated with data from the file.
|
327
|
+
|
328
|
+
Raises
|
329
|
+
------
|
330
|
+
ValueError
|
331
|
+
If an unsupported file format is specified.
|
332
|
+
"""
|
333
|
+
mapping = {"text": cls._from_text, "relion": cls._from_relion_star}
|
334
|
+
if file_format is None:
|
335
|
+
file_format = "text"
|
336
|
+
if filename.lower().endswith(".star"):
|
337
|
+
file_format = "relion"
|
338
|
+
|
339
|
+
func = mapping.get(file_format, None)
|
340
|
+
if func is None:
|
341
|
+
raise ValueError(
|
342
|
+
f"{file_format} not implemented. Supported are {','.join(mapping.keys())}."
|
343
|
+
)
|
344
|
+
|
345
|
+
translations, rotations, scores, details, *_ = func(filename=filename, **kwargs)
|
346
|
+
return cls(
|
347
|
+
translations=translations,
|
348
|
+
rotations=rotations,
|
349
|
+
scores=scores,
|
350
|
+
details=details,
|
351
|
+
)
|
352
|
+
|
353
|
+
@staticmethod
|
354
|
+
def _from_text(
|
355
|
+
filename: str,
|
356
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
357
|
+
"""
|
358
|
+
Read orientations from a text file.
|
359
|
+
|
360
|
+
Parameters
|
361
|
+
----------
|
362
|
+
filename : str
|
363
|
+
The name of the file from which to read the orientations.
|
364
|
+
|
365
|
+
Returns
|
366
|
+
-------
|
367
|
+
Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]
|
368
|
+
A tuple containing numpy arrays for translations, rotations, scores,
|
369
|
+
and details.
|
370
|
+
|
371
|
+
Notes
|
372
|
+
-----
|
373
|
+
The text file is expected to have a header and data in columns corresponding to
|
374
|
+
z, y, x, euler_z, euler_y, euler_x, score, detail.
|
375
|
+
"""
|
376
|
+
with open(filename, mode="r", encoding="utf-8") as infile:
|
377
|
+
data = [x.strip().split("\t") for x in infile.read().split("\n")]
|
378
|
+
_ = data.pop(0)
|
379
|
+
|
380
|
+
translation, rotation, score, detail = [], [], [], []
|
381
|
+
for candidate in data:
|
382
|
+
if len(candidate) <= 1:
|
383
|
+
continue
|
384
|
+
if len(candidate) != 8:
|
385
|
+
candidate.append(-1)
|
386
|
+
|
387
|
+
candidate = [float(x) for x in candidate]
|
388
|
+
translation.append((candidate[0], candidate[1], candidate[2]))
|
389
|
+
rotation.append((candidate[3], candidate[4], candidate[5]))
|
390
|
+
score.append(candidate[6])
|
391
|
+
detail.append(candidate[7])
|
392
|
+
|
393
|
+
translation = np.vstack(translation).astype(int)
|
394
|
+
rotation = np.vstack(rotation).astype(float)
|
395
|
+
score = np.array(score).astype(float)
|
396
|
+
detail = np.array(detail).astype(float)
|
397
|
+
|
398
|
+
return translation, rotation, score, detail
|
399
|
+
|
400
|
+
@staticmethod
|
401
|
+
def _parse_star(filename: str, delimiter: str = None) -> Dict:
|
402
|
+
pattern = re.compile(r"\s*#.*")
|
403
|
+
with open(filename, mode="r", encoding="utf-8") as infile:
|
404
|
+
data = infile.read()
|
405
|
+
|
406
|
+
data = deque(filter(lambda line: line and line[0] != "#", data.split("\n")))
|
407
|
+
|
408
|
+
ret, category, block = {}, None, []
|
409
|
+
while data:
|
410
|
+
line = data.popleft()
|
411
|
+
|
412
|
+
if line.startswith("data") and not line.startswith("_"):
|
413
|
+
if category != line and category is not None:
|
414
|
+
headers = list(ret[category].keys())
|
415
|
+
headers = [pattern.sub("", x) for x in headers]
|
416
|
+
ret[category] = {
|
417
|
+
header: list(column)
|
418
|
+
for header, column in zip(headers, zip(*block))
|
419
|
+
}
|
420
|
+
block.clear()
|
421
|
+
category = line
|
422
|
+
if category not in ret:
|
423
|
+
ret[category] = {}
|
424
|
+
continue
|
425
|
+
|
426
|
+
if line.startswith("_"):
|
427
|
+
ret[category][line] = []
|
428
|
+
continue
|
429
|
+
|
430
|
+
if line.startswith("loop"):
|
431
|
+
continue
|
432
|
+
|
433
|
+
line_split = line.split(delimiter)
|
434
|
+
if len(line_split):
|
435
|
+
block.append(line_split)
|
436
|
+
|
437
|
+
headers = list(ret[category].keys())
|
438
|
+
headers = [pattern.sub("", x) for x in headers]
|
439
|
+
ret[category] = {
|
440
|
+
header: list(column) for header, column in zip(headers, zip(*block))
|
441
|
+
}
|
442
|
+
return ret
|
443
|
+
|
444
|
+
@classmethod
|
445
|
+
def _from_relion_star(
|
446
|
+
cls, filename: str, delimiter: str = None
|
447
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
448
|
+
ret = cls._parse_star(filename=filename, delimiter=delimiter)
|
449
|
+
ret = ret["data_particles"]
|
450
|
+
|
451
|
+
translation = (
|
452
|
+
np.vstack(
|
453
|
+
(ret["_rlnCoordinateZ"], ret["_rlnCoordinateY"], ret["_rlnCoordinateX"])
|
454
|
+
)
|
455
|
+
.astype(np.float32)
|
456
|
+
.astype(int)
|
457
|
+
.T
|
458
|
+
)
|
459
|
+
|
460
|
+
rotation = (
|
461
|
+
np.vstack((ret["_rlnAngleRot"], ret["_rlnAngleTilt"], ret["_rlnAnglePsi"]))
|
462
|
+
.astype(np.float32)
|
463
|
+
.T
|
464
|
+
)
|
465
|
+
|
466
|
+
rotation = Rotation.from_euler("xyx", rotation, degrees=True)
|
467
|
+
rotation = rotation.as_euler(seq="zyx", degrees=True)
|
468
|
+
score = np.ones(translation.shape[0])
|
469
|
+
detail = np.ones(translation.shape[0]) * 1
|
470
|
+
|
471
|
+
return translation, rotation, score, detail
|
472
|
+
|
473
|
+
def get_extraction_slices(
|
474
|
+
self,
|
475
|
+
target_shape: Tuple[int],
|
476
|
+
extraction_shape: Tuple[int],
|
477
|
+
drop_out_of_box: bool = False,
|
478
|
+
return_orientations: bool = False,
|
479
|
+
) -> "Orientations":
|
480
|
+
"""
|
481
|
+
Calculate slices for extracting regions of interest within a larger array.
|
482
|
+
|
483
|
+
Parameters
|
484
|
+
----------
|
485
|
+
target_shape : Tuple[int]
|
486
|
+
The shape of the target array within which regions are to be extracted.
|
487
|
+
extraction_shape : Tuple[int]
|
488
|
+
The shape of the regions to be extracted.
|
489
|
+
drop_out_of_box : bool, optional
|
490
|
+
If True, drop regions that extend beyond the target array boundary, by default False.
|
491
|
+
return_orientations : bool, optional
|
492
|
+
If True, return orientations along with slices, by default False.
|
493
|
+
|
494
|
+
Returns
|
495
|
+
-------
|
496
|
+
Union[Tuple[List[slice]], Tuple["Orientations", List[slice], List[slice]]]
|
497
|
+
If return_orientations is False, returns a tuple containing slices for candidate
|
498
|
+
regions and observation regions.
|
499
|
+
If return_orientations is True, returns a tuple containing orientations along
|
500
|
+
with slices for candidate regions and observation regions.
|
501
|
+
|
502
|
+
Raises
|
503
|
+
------
|
504
|
+
SystemExit
|
505
|
+
If no peak remains after filtering, indicating an error.
|
506
|
+
"""
|
507
|
+
left_pad = np.divide(extraction_shape, 2).astype(int)
|
508
|
+
right_pad = np.add(left_pad, np.mod(extraction_shape, 2)).astype(int)
|
509
|
+
|
510
|
+
obs_start = np.subtract(self.translations, left_pad)
|
511
|
+
obs_stop = np.add(self.translations, right_pad)
|
512
|
+
|
513
|
+
cand_start = np.subtract(np.maximum(obs_start, 0), obs_start)
|
514
|
+
cand_stop = np.subtract(obs_stop, np.minimum(obs_stop, target_shape))
|
515
|
+
cand_stop = np.subtract(extraction_shape, cand_stop)
|
516
|
+
obs_start = np.maximum(obs_start, 0)
|
517
|
+
obs_stop = np.minimum(obs_stop, target_shape)
|
518
|
+
|
519
|
+
subset = self
|
520
|
+
if drop_out_of_box:
|
521
|
+
stops = np.subtract(cand_stop, extraction_shape)
|
522
|
+
keep_peaks = (
|
523
|
+
np.sum(
|
524
|
+
np.multiply(cand_start == 0, stops == 0),
|
525
|
+
axis=1,
|
526
|
+
)
|
527
|
+
== self.translations.shape[1]
|
528
|
+
)
|
529
|
+
n_remaining = keep_peaks.sum()
|
530
|
+
if n_remaining == 0:
|
531
|
+
print(
|
532
|
+
"No peak remaining after filtering. Started with"
|
533
|
+
f" {self.translations.shape[0]} filtered to {n_remaining}."
|
534
|
+
" Consider reducing min_distance, increase num_peaks or use"
|
535
|
+
" a different peak caller."
|
536
|
+
)
|
537
|
+
exit(-1)
|
538
|
+
|
539
|
+
cand_start = cand_start[keep_peaks,]
|
540
|
+
cand_stop = cand_stop[keep_peaks,]
|
541
|
+
obs_start = obs_start[keep_peaks,]
|
542
|
+
obs_stop = obs_stop[keep_peaks,]
|
543
|
+
subset = self[keep_peaks]
|
544
|
+
|
545
|
+
cand_start, cand_stop = cand_start.astype(int), cand_stop.astype(int)
|
546
|
+
obs_start, obs_stop = obs_start.astype(int), obs_stop.astype(int)
|
547
|
+
|
548
|
+
candidate_slices = [
|
549
|
+
tuple(slice(s, e) for s, e in zip(start_row, stop_row))
|
550
|
+
for start_row, stop_row in zip(cand_start, cand_stop)
|
551
|
+
]
|
552
|
+
|
553
|
+
observation_slices = [
|
554
|
+
tuple(slice(s, e) for s, e in zip(start_row, stop_row))
|
555
|
+
for start_row, stop_row in zip(obs_start, obs_stop)
|
556
|
+
]
|
557
|
+
|
558
|
+
if return_orientations:
|
559
|
+
return subset, candidate_slices, observation_slices
|
560
|
+
|
561
|
+
return candidate_slices, observation_slices
|
tme/preprocessor.py
CHANGED
@@ -654,12 +654,9 @@ class Preprocessor:
|
|
654
654
|
array = template.copy()
|
655
655
|
interpolation_box = array.shape
|
656
656
|
|
657
|
-
print(array.shape)
|
658
|
-
|
659
657
|
for k in range(template.ndim):
|
660
658
|
array = decimate(array, q=level, axis=k)
|
661
659
|
|
662
|
-
print(array.shape)
|
663
660
|
template = zoom(array, np.divide(template.shape, array.shape))
|
664
661
|
template = self.interpolate_box(box=interpolation_box, arr=template)
|
665
662
|
|
@@ -768,21 +765,24 @@ class Preprocessor:
|
|
768
765
|
sigma = sigma_factor * resolution
|
769
766
|
sigma_grid = sigma / sampling_rate
|
770
767
|
sigma_grid2 = sigma_grid * sigma_grid
|
771
|
-
for index, point in enumerate(np.rollaxis(positions, 0)):
|
772
|
-
starts = np.maximum(np.ceil(point - cutoff_value * sigma_grid), 0).astype(
|
773
|
-
int
|
774
|
-
)
|
775
|
-
stops = np.minimum(
|
776
|
-
np.floor(point + cutoff_value * sigma_grid), shape
|
777
|
-
).astype(int)
|
778
768
|
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
769
|
+
starts = np.maximum(np.ceil(positions - cutoff_value * sigma_grid), 0).astype(
|
770
|
+
int
|
771
|
+
)
|
772
|
+
stops = np.minimum(
|
773
|
+
np.floor(positions + cutoff_value * sigma_grid), shape
|
774
|
+
).astype(int)
|
775
|
+
ranges = tuple(tuple(zip(start, stop)) for start, stop in zip(starts, stops))
|
776
|
+
|
777
|
+
positions = positions.reshape(
|
778
|
+
*positions.shape, *tuple(1 for _ in range(positions.shape[1]))
|
779
|
+
)
|
780
|
+
for index in range(positions.shape[0]):
|
781
|
+
grid_index = np.meshgrid(*[range(*coord) for coord in ranges[index]])
|
782
|
+
distances = np.sum(
|
783
|
+
np.square(np.subtract(grid_index, positions[index])),
|
784
|
+
dtype=np.float32,
|
785
|
+
axis=0,
|
786
786
|
)
|
787
787
|
np.add.at(
|
788
788
|
out,
|
@@ -1131,6 +1131,7 @@ class Preprocessor:
|
|
1131
1131
|
stop_tilt: float,
|
1132
1132
|
tilt_step: float,
|
1133
1133
|
shape: Tuple[int],
|
1134
|
+
tilt_angles: Tuple[float] = None,
|
1134
1135
|
opening_axis: int = 0,
|
1135
1136
|
tilt_axis: int = 2,
|
1136
1137
|
sigma: float = 0,
|
@@ -1184,7 +1185,9 @@ class Preprocessor:
|
|
1184
1185
|
:py:meth:`Preprocessor.wedge_mask`
|
1185
1186
|
:py:meth:`Preprocessor.continuous_wedge_mask`
|
1186
1187
|
"""
|
1187
|
-
tilt_angles
|
1188
|
+
if tilt_angles is None:
|
1189
|
+
tilt_angles = np.arange(-start_tilt, stop_tilt + tilt_step, tilt_step)
|
1190
|
+
|
1188
1191
|
plane = np.zeros((shape[opening_axis], shape[tilt_axis]), dtype=np.float32)
|
1189
1192
|
subset = tuple(
|
1190
1193
|
slice(None) if i != 0 else slice(x // 2, x // 2 + 1)
|
tme/structure.py
CHANGED
@@ -26,45 +26,10 @@ from .types import NDArray
|
|
26
26
|
|
27
27
|
@dataclass(repr=False)
|
28
28
|
class Structure:
|
29
|
-
"""
|
29
|
+
"""
|
30
|
+
Represents atomic structures in accordance with the Protein Data Bank (PDB)
|
30
31
|
format specification.
|
31
32
|
|
32
|
-
Attributes
|
33
|
-
----------
|
34
|
-
record_type : NDArray
|
35
|
-
Type of the record, e.g., ATOM, HETATM. Array shape = (n,)
|
36
|
-
atom_serial_number : NDArray
|
37
|
-
Serial number assigned to each atom. Array shape = (n,)
|
38
|
-
atom_name : NDArray
|
39
|
-
Standardized names for each atom. Array shape = (n,)
|
40
|
-
atom_coordinate : NDArray
|
41
|
-
The 3D Cartesian coordinates of each atom in x, y, z. Array shape = (n,3 )
|
42
|
-
alternate_location_indicator : NDArray
|
43
|
-
Indicator for alternate locations of an atom if it exists in multiple places.
|
44
|
-
Array shape = (n,)
|
45
|
-
residue_name : NDArray
|
46
|
-
Standard residue names where each atom belongs. Array shape = (n,)
|
47
|
-
chain_identifier : NDArray
|
48
|
-
Identifier for the chain where each atom is located. Array shape = (n,)
|
49
|
-
residue_sequence_number : NDArray
|
50
|
-
Sequence number of the residue in the protein chain for each atom.
|
51
|
-
Array shape = (n,)
|
52
|
-
code_for_residue_insertion : NDArray
|
53
|
-
Code to denote any residue insertion. Array shape = (n,)
|
54
|
-
occupancy : NDArray
|
55
|
-
Occupancy factor of each atom, indicating the fraction of time the atom
|
56
|
-
is located at its position. Array shape = (n,)
|
57
|
-
temperature_factor : NDArray
|
58
|
-
Measure of the atomic displacement or B-factor for each atom. Array shape = (n,)
|
59
|
-
segment_identifier : NDArray
|
60
|
-
Identifier for the segment where each atom belongs. Array shape = (n,)
|
61
|
-
element_symbol : NDArray
|
62
|
-
Atomic element symbol for each atom. Array shape = (n,)
|
63
|
-
charge : NDArray
|
64
|
-
Charge on the atom. Array shape = (n,)
|
65
|
-
details : dict
|
66
|
-
Any additional or auxiliary details. Array shape = (n,)
|
67
|
-
|
68
33
|
References
|
69
34
|
----------
|
70
35
|
.. [1] https://www.cgl.ucsf.edu/chimera/docs/UsersGuide/tutorials/pdbintro.html
|