pytme 0.1.8__cp311-cp311-macosx_14_0_arm64.whl → 0.2.0__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.0.data/scripts/match_template.py +1019 -0
- pytme-0.2.0.data/scripts/postprocess.py +570 -0
- {pytme-0.1.8.data → pytme-0.2.0.data}/scripts/preprocessor_gui.py +244 -60
- {pytme-0.1.8.dist-info → pytme-0.2.0.dist-info}/METADATA +3 -1
- pytme-0.2.0.dist-info/RECORD +72 -0
- {pytme-0.1.8.dist-info → pytme-0.2.0.dist-info}/WHEEL +1 -1
- scripts/extract_candidates.py +218 -0
- scripts/match_template.py +459 -218
- pytme-0.1.8.data/scripts/match_template.py → scripts/match_template_filters.py +459 -218
- 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 +533 -78
- tme/backends/cupy_backend.py +80 -15
- tme/backends/npfftw_backend.py +35 -6
- tme/backends/pytorch_backend.py +15 -7
- tme/density.py +173 -78
- tme/extensions.cpython-311-darwin.so +0 -0
- tme/matching_constrained.py +195 -0
- tme/matching_data.py +78 -32
- tme/matching_exhaustive.py +369 -221
- tme/matching_memory.py +1 -0
- tme/matching_optimization.py +753 -649
- tme/matching_utils.py +152 -8
- tme/orientations.py +561 -0
- tme/preprocessing/__init__.py +2 -0
- tme/preprocessing/_utils.py +176 -0
- tme/preprocessing/composable_filter.py +30 -0
- tme/preprocessing/compose.py +52 -0
- tme/preprocessing/frequency_filters.py +322 -0
- tme/preprocessing/tilt_series.py +967 -0
- tme/preprocessor.py +35 -25
- 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.0.data}/scripts/estimate_ram_usage.py +0 -0
- {pytme-0.1.8.data → pytme-0.2.0.data}/scripts/preprocess.py +0 -0
- {pytme-0.1.8.dist-info → pytme-0.2.0.dist-info}/LICENSE +0 -0
- {pytme-0.1.8.dist-info → pytme-0.2.0.dist-info}/entry_points.txt +0 -0
- {pytme-0.1.8.dist-info → pytme-0.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,570 @@
|
|
1
|
+
#!python
|
2
|
+
""" CLI to simplify analysing the output of match_template.py.
|
3
|
+
|
4
|
+
Copyright (c) 2023 European Molecular Biology Laboratory
|
5
|
+
|
6
|
+
Author: Valentin Maurer <valentin.maurer@embl-hamburg.de>
|
7
|
+
"""
|
8
|
+
import argparse
|
9
|
+
from sys import exit
|
10
|
+
from os import getcwd
|
11
|
+
from os.path import join, abspath
|
12
|
+
from typing import List
|
13
|
+
from os.path import splitext
|
14
|
+
|
15
|
+
import numpy as np
|
16
|
+
from numpy.typing import NDArray
|
17
|
+
from scipy.special import erfcinv
|
18
|
+
|
19
|
+
from tme import Density, Structure, Orientations
|
20
|
+
from tme.analyzer import (
|
21
|
+
PeakCallerSort,
|
22
|
+
PeakCallerMaximumFilter,
|
23
|
+
PeakCallerFast,
|
24
|
+
PeakCallerRecursiveMasking,
|
25
|
+
PeakCallerScipy,
|
26
|
+
)
|
27
|
+
from tme.matching_utils import (
|
28
|
+
load_pickle,
|
29
|
+
euler_to_rotationmatrix,
|
30
|
+
euler_from_rotationmatrix,
|
31
|
+
)
|
32
|
+
|
33
|
+
PEAK_CALLERS = {
|
34
|
+
"PeakCallerSort": PeakCallerSort,
|
35
|
+
"PeakCallerMaximumFilter": PeakCallerMaximumFilter,
|
36
|
+
"PeakCallerFast": PeakCallerFast,
|
37
|
+
"PeakCallerRecursiveMasking": PeakCallerRecursiveMasking,
|
38
|
+
"PeakCallerScipy": PeakCallerScipy,
|
39
|
+
}
|
40
|
+
|
41
|
+
|
42
|
+
def parse_args():
|
43
|
+
parser = argparse.ArgumentParser(
|
44
|
+
description="Peak Calling for Template Matching Outputs"
|
45
|
+
)
|
46
|
+
|
47
|
+
input_group = parser.add_argument_group("Input")
|
48
|
+
output_group = parser.add_argument_group("Output")
|
49
|
+
peak_group = parser.add_argument_group("Peak Calling")
|
50
|
+
additional_group = parser.add_argument_group("Additional Parameters")
|
51
|
+
|
52
|
+
input_group.add_argument(
|
53
|
+
"--input_file",
|
54
|
+
required=True,
|
55
|
+
nargs="+",
|
56
|
+
help="Path to the output of match_template.py.",
|
57
|
+
)
|
58
|
+
input_group.add_argument(
|
59
|
+
"--target_mask",
|
60
|
+
required=False,
|
61
|
+
type=str,
|
62
|
+
help="Path to an optional mask applied to template matching scores.",
|
63
|
+
)
|
64
|
+
input_group.add_argument(
|
65
|
+
"--orientations",
|
66
|
+
required=False,
|
67
|
+
type=str,
|
68
|
+
help="Path to file generated using output_format orientations. Can be filtered "
|
69
|
+
"to exclude false-positive peaks. If this file is provided, peak calling "
|
70
|
+
"is skipped and corresponding parameters ignored.",
|
71
|
+
)
|
72
|
+
|
73
|
+
output_group.add_argument(
|
74
|
+
"--output_prefix",
|
75
|
+
required=True,
|
76
|
+
help="Output filename, extension will be added based on output_format.",
|
77
|
+
)
|
78
|
+
output_group.add_argument(
|
79
|
+
"--output_format",
|
80
|
+
choices=[
|
81
|
+
"orientations",
|
82
|
+
"alignment",
|
83
|
+
"extraction",
|
84
|
+
"relion",
|
85
|
+
"backmapping",
|
86
|
+
"average",
|
87
|
+
],
|
88
|
+
default="orientations",
|
89
|
+
help="Available output formats:"
|
90
|
+
"orientations (translation, rotation, and score), "
|
91
|
+
"alignment (aligned template to target based on orientations), "
|
92
|
+
"extraction (extract regions around peaks from targets, i.e. subtomograms), "
|
93
|
+
"relion (perform extraction step and generate corresponding star files), "
|
94
|
+
"backmapping (map template to target using identified peaks),"
|
95
|
+
"average (extract matched regions from target and average them).",
|
96
|
+
)
|
97
|
+
|
98
|
+
peak_group.add_argument(
|
99
|
+
"--peak_caller",
|
100
|
+
choices=list(PEAK_CALLERS.keys()),
|
101
|
+
default="PeakCallerScipy",
|
102
|
+
help="Peak caller for local maxima identification.",
|
103
|
+
)
|
104
|
+
peak_group.add_argument(
|
105
|
+
"--minimum_score",
|
106
|
+
type=float,
|
107
|
+
default=None,
|
108
|
+
help="Minimum score from which peaks will be considered.",
|
109
|
+
)
|
110
|
+
peak_group.add_argument(
|
111
|
+
"--maximum_score",
|
112
|
+
type=float,
|
113
|
+
default=None,
|
114
|
+
help="Maximum score until which peaks will be considered.",
|
115
|
+
)
|
116
|
+
peak_group.add_argument(
|
117
|
+
"--min_distance",
|
118
|
+
type=int,
|
119
|
+
default=5,
|
120
|
+
help="Minimum distance between peaks.",
|
121
|
+
)
|
122
|
+
peak_group.add_argument(
|
123
|
+
"--min_boundary_distance",
|
124
|
+
type=int,
|
125
|
+
default=0,
|
126
|
+
help="Minimum distance of peaks to target edges.",
|
127
|
+
)
|
128
|
+
peak_group.add_argument(
|
129
|
+
"--mask_edges",
|
130
|
+
action="store_true",
|
131
|
+
default=False,
|
132
|
+
help="Whether candidates should not be identified from scores that were "
|
133
|
+
"computed from padded densities. Superseded by min_boundary_distance.",
|
134
|
+
)
|
135
|
+
peak_group.add_argument(
|
136
|
+
"--number_of_peaks",
|
137
|
+
type=int,
|
138
|
+
default=None,
|
139
|
+
required=False,
|
140
|
+
help="Upper limit of peaks to call, subject to filtering parameters. Default 1000. "
|
141
|
+
"If minimum_score is provided all peaks scoring higher will be reported.",
|
142
|
+
)
|
143
|
+
peak_group.add_argument(
|
144
|
+
"--peak_oversampling",
|
145
|
+
type=int,
|
146
|
+
default=1,
|
147
|
+
help="1 / factor equals voxel precision, e.g. 2 detects half voxel "
|
148
|
+
"translations. Useful for matching structures to electron density maps.",
|
149
|
+
)
|
150
|
+
|
151
|
+
additional_group.add_argument(
|
152
|
+
"--subtomogram_box_size",
|
153
|
+
type=int,
|
154
|
+
default=None,
|
155
|
+
help="Subtomogram box size, by default equal to the centered template. Will be "
|
156
|
+
"padded to even values if output_format is relion.",
|
157
|
+
)
|
158
|
+
additional_group.add_argument(
|
159
|
+
"--mask_subtomograms",
|
160
|
+
action="store_true",
|
161
|
+
default=False,
|
162
|
+
help="Whether to mask subtomograms using the template mask. The mask will be "
|
163
|
+
"rotated according to determined angles.",
|
164
|
+
)
|
165
|
+
additional_group.add_argument(
|
166
|
+
"--invert_target_contrast",
|
167
|
+
action="store_true",
|
168
|
+
default=False,
|
169
|
+
help="Whether to invert the target contrast.",
|
170
|
+
)
|
171
|
+
additional_group.add_argument(
|
172
|
+
"--wedge_mask",
|
173
|
+
type=str,
|
174
|
+
default=None,
|
175
|
+
help="Path to file used as ctf_mask for output_format relion.",
|
176
|
+
)
|
177
|
+
additional_group.add_argument(
|
178
|
+
"--n_false_positives",
|
179
|
+
type=int,
|
180
|
+
default=None,
|
181
|
+
required=False,
|
182
|
+
help="Number of accepted false-positives picks to determine minimum score.",
|
183
|
+
)
|
184
|
+
|
185
|
+
args = parser.parse_args()
|
186
|
+
|
187
|
+
if args.wedge_mask is not None:
|
188
|
+
args.wedge_mask = abspath(args.wedge_mask)
|
189
|
+
|
190
|
+
if args.output_format == "relion" and args.subtomogram_box_size is not None:
|
191
|
+
args.subtomogram_box_size += args.subtomogram_box_size % 2
|
192
|
+
|
193
|
+
if args.orientations is not None:
|
194
|
+
args.orientations = Orientations.from_file(filename=args.orientations)
|
195
|
+
|
196
|
+
if args.minimum_score is not None or args.n_false_positives is not None:
|
197
|
+
args.number_of_peaks = np.iinfo(np.int64).max
|
198
|
+
else:
|
199
|
+
args.number_of_peaks = 1000
|
200
|
+
|
201
|
+
return args
|
202
|
+
|
203
|
+
|
204
|
+
def load_template(filepath: str, sampling_rate: NDArray, center: bool = True):
|
205
|
+
try:
|
206
|
+
template = Density.from_file(filepath)
|
207
|
+
center_of_mass = template.center_of_mass(template.data)
|
208
|
+
template_is_density = True
|
209
|
+
except ValueError:
|
210
|
+
template = Structure.from_file(filepath)
|
211
|
+
center_of_mass = template.center_of_mass()[::-1]
|
212
|
+
template = Density.from_structure(template, sampling_rate=sampling_rate)
|
213
|
+
template_is_density = False
|
214
|
+
|
215
|
+
translation = np.zeros_like(center_of_mass)
|
216
|
+
if center:
|
217
|
+
template, translation = template.centered(0)
|
218
|
+
|
219
|
+
return template, center_of_mass, translation, template_is_density
|
220
|
+
|
221
|
+
|
222
|
+
def merge_outputs(data, filepaths: List[str], args):
|
223
|
+
if len(filepaths) == 0:
|
224
|
+
return data, 1
|
225
|
+
|
226
|
+
if data[0].ndim != data[2].ndim:
|
227
|
+
return data, 1
|
228
|
+
|
229
|
+
from tme.matching_exhaustive import _normalize_under_mask
|
230
|
+
|
231
|
+
def _norm_scores(data, args):
|
232
|
+
target_origin, _, sampling_rate, cli_args = data[-1]
|
233
|
+
|
234
|
+
_, template_extension = splitext(cli_args.template)
|
235
|
+
ret = load_template(
|
236
|
+
filepath=cli_args.template,
|
237
|
+
sampling_rate=sampling_rate,
|
238
|
+
center=not cli_args.no_centering,
|
239
|
+
)
|
240
|
+
template, center_of_mass, translation, template_is_density = ret
|
241
|
+
|
242
|
+
if args.mask_edges and args.min_boundary_distance == 0:
|
243
|
+
max_shape = np.max(template.shape)
|
244
|
+
args.min_boundary_distance = np.ceil(np.divide(max_shape, 2))
|
245
|
+
|
246
|
+
target_mask = 1
|
247
|
+
if args.target_mask is not None:
|
248
|
+
target_mask = Density.from_file(args.target_mask).data
|
249
|
+
elif cli_args.target_mask is not None:
|
250
|
+
target_mask = Density.from_file(args.target_mask).data
|
251
|
+
|
252
|
+
mask = np.ones_like(data[0])
|
253
|
+
np.multiply(mask, target_mask, out=mask)
|
254
|
+
|
255
|
+
cropped_shape = np.subtract(
|
256
|
+
mask.shape, np.multiply(args.min_boundary_distance, 2)
|
257
|
+
).astype(int)
|
258
|
+
mask[cropped_shape] = 0
|
259
|
+
_normalize_under_mask(template=data[0], mask=mask, mask_intensity=mask.sum())
|
260
|
+
return data[0]
|
261
|
+
|
262
|
+
entities = np.zeros_like(data[0])
|
263
|
+
data[0] = _norm_scores(data=data, args=args)
|
264
|
+
for index, filepath in enumerate(filepaths):
|
265
|
+
new_scores = _norm_scores(data=load_pickle(filepath), args=args)
|
266
|
+
indices = new_scores > data[0]
|
267
|
+
entities[indices] = index + 1
|
268
|
+
data[0][indices] = new_scores[indices]
|
269
|
+
|
270
|
+
return data, entities
|
271
|
+
|
272
|
+
|
273
|
+
def main():
|
274
|
+
args = parse_args()
|
275
|
+
data = load_pickle(args.input_file[0])
|
276
|
+
|
277
|
+
target_origin, _, sampling_rate, cli_args = data[-1]
|
278
|
+
|
279
|
+
_, template_extension = splitext(cli_args.template)
|
280
|
+
ret = load_template(
|
281
|
+
filepath=cli_args.template,
|
282
|
+
sampling_rate=sampling_rate,
|
283
|
+
center=not cli_args.no_centering,
|
284
|
+
)
|
285
|
+
template, center_of_mass, translation, template_is_density = ret
|
286
|
+
|
287
|
+
if args.output_format == "relion" and args.subtomogram_box_size is None:
|
288
|
+
new_shape = np.add(template.shape, np.mod(template.shape, 2))
|
289
|
+
new_shape = np.repeat(new_shape.max(), new_shape.size).astype(int)
|
290
|
+
print(f"Padding template from {template.shape} to {new_shape} for RELION.")
|
291
|
+
template.pad(new_shape)
|
292
|
+
|
293
|
+
template_mask = template.empty
|
294
|
+
template_mask.data[:] = 1
|
295
|
+
if cli_args.template_mask is not None:
|
296
|
+
template_mask = Density.from_file(cli_args.template_mask)
|
297
|
+
template_mask.pad(template.shape, center=False)
|
298
|
+
origin_translation = np.divide(
|
299
|
+
np.subtract(template.origin, template_mask.origin), template.sampling_rate
|
300
|
+
)
|
301
|
+
translation = np.add(translation, origin_translation)
|
302
|
+
|
303
|
+
template_mask = template_mask.rigid_transform(
|
304
|
+
rotation_matrix=np.eye(template_mask.data.ndim),
|
305
|
+
translation=-translation,
|
306
|
+
order=1,
|
307
|
+
)
|
308
|
+
|
309
|
+
if args.mask_edges and args.min_boundary_distance == 0:
|
310
|
+
max_shape = np.max(template.shape)
|
311
|
+
args.min_boundary_distance = np.ceil(np.divide(max_shape, 2))
|
312
|
+
|
313
|
+
# data, entities = merge_outputs(data=data, filepaths=args.input_file[1:], args=args)
|
314
|
+
|
315
|
+
orientations = args.orientations
|
316
|
+
if orientations is None:
|
317
|
+
translations, rotations, scores, details = [], [], [], []
|
318
|
+
# Output is MaxScoreOverRotations
|
319
|
+
if data[0].ndim == data[2].ndim:
|
320
|
+
scores, offset, rotation_array, rotation_mapping, meta = data
|
321
|
+
|
322
|
+
if args.target_mask is not None:
|
323
|
+
target_mask = Density.from_file(args.target_mask)
|
324
|
+
scores = scores * target_mask.data
|
325
|
+
|
326
|
+
if args.n_false_positives is not None:
|
327
|
+
args.n_false_positives = max(args.n_false_positives, 1)
|
328
|
+
cropped_shape = np.subtract(
|
329
|
+
scores.shape, np.multiply(args.min_boundary_distance, 2)
|
330
|
+
).astype(int)
|
331
|
+
|
332
|
+
cropped_shape = tuple(
|
333
|
+
slice(
|
334
|
+
int(args.min_boundary_distance),
|
335
|
+
int(x - args.min_boundary_distance),
|
336
|
+
)
|
337
|
+
for x in scores.shape
|
338
|
+
)
|
339
|
+
# Rickgauer et al. 2017
|
340
|
+
n_correlations = np.size(scores[cropped_shape]) * len(rotation_mapping)
|
341
|
+
minimum_score = np.multiply(
|
342
|
+
erfcinv(2 * args.n_false_positives / n_correlations),
|
343
|
+
np.sqrt(2) * np.std(scores[cropped_shape]),
|
344
|
+
)
|
345
|
+
print(f"Determined minimum score cutoff: {minimum_score}.")
|
346
|
+
minimum_score = max(minimum_score, 0)
|
347
|
+
args.minimum_score = minimum_score
|
348
|
+
|
349
|
+
peak_caller = PEAK_CALLERS[args.peak_caller](
|
350
|
+
number_of_peaks=args.number_of_peaks,
|
351
|
+
min_distance=args.min_distance,
|
352
|
+
min_boundary_distance=args.min_boundary_distance,
|
353
|
+
)
|
354
|
+
if args.minimum_score is not None:
|
355
|
+
args.number_of_peaks = np.inf
|
356
|
+
|
357
|
+
peak_caller(
|
358
|
+
scores,
|
359
|
+
rotation_matrix=np.eye(3),
|
360
|
+
mask=template.data,
|
361
|
+
rotation_mapping=rotation_mapping,
|
362
|
+
rotation_array=rotation_array,
|
363
|
+
minimum_score=args.minimum_score,
|
364
|
+
)
|
365
|
+
candidates = peak_caller.merge(
|
366
|
+
candidates=[tuple(peak_caller)],
|
367
|
+
number_of_peaks=args.number_of_peaks,
|
368
|
+
min_distance=args.min_distance,
|
369
|
+
min_boundary_distance=args.min_boundary_distance,
|
370
|
+
)
|
371
|
+
if len(candidates) == 0:
|
372
|
+
print("Found no peaks. Consider changing peak calling parameters.")
|
373
|
+
exit(-1)
|
374
|
+
|
375
|
+
for translation, _, score, detail in zip(*candidates):
|
376
|
+
rotations.append(rotation_mapping[rotation_array[tuple(translation)]])
|
377
|
+
|
378
|
+
else:
|
379
|
+
candidates = data
|
380
|
+
translation, rotation, *_ = data
|
381
|
+
for i in range(translation.shape[0]):
|
382
|
+
rotations.append(euler_from_rotationmatrix(rotation[i]))
|
383
|
+
|
384
|
+
rotations = np.vstack(rotations).astype(float)
|
385
|
+
translations, scores, details = candidates[0], candidates[2], candidates[3]
|
386
|
+
orientations = Orientations(
|
387
|
+
translations=translations,
|
388
|
+
rotations=rotations,
|
389
|
+
scores=scores,
|
390
|
+
details=details,
|
391
|
+
)
|
392
|
+
|
393
|
+
if args.minimum_score is not None:
|
394
|
+
keep = orientations.scores >= args.minimum_score
|
395
|
+
orientations = orientations[keep]
|
396
|
+
|
397
|
+
if args.maximum_score is not None:
|
398
|
+
keep = orientations.scores <= args.maximum_score
|
399
|
+
orientations = orientations[keep]
|
400
|
+
|
401
|
+
if args.output_format == "orientations":
|
402
|
+
orientations.to_file(filename=f"{args.output_prefix}.tsv", file_format="text")
|
403
|
+
exit(0)
|
404
|
+
|
405
|
+
target = Density.from_file(cli_args.target)
|
406
|
+
if args.invert_target_contrast:
|
407
|
+
if args.output_format == "relion":
|
408
|
+
target.data = target.data * -1
|
409
|
+
target.data = np.divide(
|
410
|
+
np.subtract(target.data, target.data.mean()), target.data.std()
|
411
|
+
)
|
412
|
+
else:
|
413
|
+
target.data = (
|
414
|
+
-np.divide(
|
415
|
+
np.subtract(target.data, target.data.min()),
|
416
|
+
np.subtract(target.data.max(), target.data.min()),
|
417
|
+
)
|
418
|
+
+ 1
|
419
|
+
)
|
420
|
+
|
421
|
+
if args.output_format in ("extraction", "relion"):
|
422
|
+
if not np.all(np.divide(target.shape, template.shape) > 2):
|
423
|
+
print(
|
424
|
+
"Target might be too small relative to template to extract"
|
425
|
+
" meaningful particles."
|
426
|
+
f" Target : {target.shape}, template : {template.shape}."
|
427
|
+
)
|
428
|
+
|
429
|
+
extraction_shape = template.shape
|
430
|
+
if args.subtomogram_box_size is not None:
|
431
|
+
extraction_shape = np.repeat(
|
432
|
+
args.subtomogram_box_size, len(extraction_shape)
|
433
|
+
)
|
434
|
+
|
435
|
+
orientations, cand_slices, obs_slices = orientations.get_extraction_slices(
|
436
|
+
target_shape=target.shape,
|
437
|
+
extraction_shape=extraction_shape,
|
438
|
+
drop_out_of_box=True,
|
439
|
+
return_orientations=True,
|
440
|
+
)
|
441
|
+
|
442
|
+
working_directory = getcwd()
|
443
|
+
if args.output_format == "relion":
|
444
|
+
orientations.to_file(
|
445
|
+
filename=f"{args.output_prefix}.star",
|
446
|
+
file_format="relion",
|
447
|
+
name_prefix=join(working_directory, args.output_prefix),
|
448
|
+
ctf_image=args.wedge_mask,
|
449
|
+
sampling_rate=target.sampling_rate.max(),
|
450
|
+
subtomogram_size=extraction_shape[0],
|
451
|
+
)
|
452
|
+
|
453
|
+
observations = np.zeros((len(cand_slices), *extraction_shape))
|
454
|
+
slices = zip(cand_slices, obs_slices)
|
455
|
+
for idx, (cand_slice, obs_slice) in enumerate(slices):
|
456
|
+
observations[idx][:] = np.mean(target.data[obs_slice])
|
457
|
+
observations[idx][cand_slice] = target.data[obs_slice]
|
458
|
+
|
459
|
+
for index in range(observations.shape[0]):
|
460
|
+
cand_start = [x.start for x in cand_slices[index]]
|
461
|
+
out_density = Density(
|
462
|
+
data=observations[index],
|
463
|
+
sampling_rate=sampling_rate,
|
464
|
+
origin=np.multiply(cand_start, sampling_rate),
|
465
|
+
)
|
466
|
+
if args.mask_subtomograms:
|
467
|
+
rotation_matrix = euler_to_rotationmatrix(orientations.rotations[index])
|
468
|
+
mask_transfomed = template_mask.rigid_transform(
|
469
|
+
rotation_matrix=rotation_matrix, order=1
|
470
|
+
)
|
471
|
+
out_density.data = out_density.data * mask_transfomed.data
|
472
|
+
out_density.to_file(
|
473
|
+
join(working_directory, f"{args.output_prefix}_{index}.mrc")
|
474
|
+
)
|
475
|
+
|
476
|
+
exit(0)
|
477
|
+
|
478
|
+
if args.output_format == "backmapping":
|
479
|
+
orientations, cand_slices, obs_slices = orientations.get_extraction_slices(
|
480
|
+
target_shape=target.shape,
|
481
|
+
extraction_shape=template.shape,
|
482
|
+
drop_out_of_box=True,
|
483
|
+
return_orientations=True,
|
484
|
+
)
|
485
|
+
ret, template_sum = target.empty, template.data.sum()
|
486
|
+
for index in range(len(cand_slices)):
|
487
|
+
rotation_matrix = euler_to_rotationmatrix(orientations.rotations[index])
|
488
|
+
|
489
|
+
transformed_template = template.rigid_transform(
|
490
|
+
rotation_matrix=rotation_matrix
|
491
|
+
)
|
492
|
+
transformed_template.data = np.multiply(
|
493
|
+
transformed_template.data,
|
494
|
+
np.divide(template_sum, transformed_template.data.sum()),
|
495
|
+
)
|
496
|
+
cand_slice, obs_slice = cand_slices[index], obs_slices[index]
|
497
|
+
ret.data[obs_slice] += transformed_template.data[cand_slice]
|
498
|
+
ret.to_file(f"{args.output_prefix}_backmapped.mrc")
|
499
|
+
exit(0)
|
500
|
+
|
501
|
+
if args.output_format == "average":
|
502
|
+
orientations, cand_slices, obs_slices = orientations.get_extraction_slices(
|
503
|
+
target_shape=target.shape,
|
504
|
+
extraction_shape=np.multiply(template.shape, 2),
|
505
|
+
drop_out_of_box=True,
|
506
|
+
return_orientations=True,
|
507
|
+
)
|
508
|
+
out = np.zeros_like(template.data)
|
509
|
+
out = np.zeros(np.multiply(template.shape, 2).astype(int))
|
510
|
+
for index in range(len(cand_slices)):
|
511
|
+
from scipy.spatial.transform import Rotation
|
512
|
+
|
513
|
+
rotation = Rotation.from_euler(
|
514
|
+
angles=orientations.rotations[index], seq="zyx", degrees=True
|
515
|
+
)
|
516
|
+
rotation_matrix = rotation.inv().as_matrix()
|
517
|
+
|
518
|
+
# rotation_matrix = euler_to_rotationmatrix(orientations.rotations[index])
|
519
|
+
subset = Density(target.data[obs_slices[index]])
|
520
|
+
subset = subset.rigid_transform(rotation_matrix=rotation_matrix, order=1)
|
521
|
+
|
522
|
+
np.add(out, subset.data, out=out)
|
523
|
+
out /= len(cand_slices)
|
524
|
+
ret = Density(out, sampling_rate=template.sampling_rate, origin=0)
|
525
|
+
ret.pad(template.shape, center=True)
|
526
|
+
ret.to_file(f"{args.output_prefix}_average.mrc")
|
527
|
+
exit(0)
|
528
|
+
|
529
|
+
if args.peak_oversampling > 1:
|
530
|
+
peak_caller = peak_caller = PEAK_CALLERS[args.peak_caller]()
|
531
|
+
if data[0].ndim != data[2].ndim:
|
532
|
+
print(
|
533
|
+
"Input pickle does not contain template matching scores."
|
534
|
+
" Cannot oversample peaks."
|
535
|
+
)
|
536
|
+
exit(-1)
|
537
|
+
orientations.translations = peak_caller.oversample_peaks(
|
538
|
+
score_space=data[0],
|
539
|
+
translations=orientations.translations,
|
540
|
+
oversampling_factor=args.oversampling_factor,
|
541
|
+
)
|
542
|
+
|
543
|
+
for index, (translation, angles, *_) in enumerate(orientations):
|
544
|
+
rotation_matrix = euler_to_rotationmatrix(angles)
|
545
|
+
if template_is_density:
|
546
|
+
translation = np.subtract(translation, center_of_mass)
|
547
|
+
transformed_template = template.rigid_transform(
|
548
|
+
rotation_matrix=rotation_matrix
|
549
|
+
)
|
550
|
+
new_origin = np.add(target_origin / sampling_rate, translation)
|
551
|
+
transformed_template.origin = np.multiply(new_origin, sampling_rate)
|
552
|
+
else:
|
553
|
+
template = Structure.from_file(cli_args.template)
|
554
|
+
new_center_of_mass = np.add(
|
555
|
+
np.multiply(translation, sampling_rate), target_origin
|
556
|
+
)
|
557
|
+
translation = np.subtract(new_center_of_mass, center_of_mass)
|
558
|
+
transformed_template = template.rigid_transform(
|
559
|
+
translation=translation[::-1],
|
560
|
+
rotation_matrix=rotation_matrix[::-1, ::-1],
|
561
|
+
)
|
562
|
+
# template_extension should contain '.'
|
563
|
+
transformed_template.to_file(
|
564
|
+
f"{args.output_prefix}_{index}{template_extension}"
|
565
|
+
)
|
566
|
+
index += 1
|
567
|
+
|
568
|
+
|
569
|
+
if __name__ == "__main__":
|
570
|
+
main()
|