pytme 0.1.6__cp311-cp311-macosx_14_0_arm64.whl → 0.1.8__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.
@@ -88,7 +88,7 @@ def main():
88
88
  shape2=template_box,
89
89
  matching_method=args.score,
90
90
  ncores=args.ncores,
91
- analyzer_method="MaxScoreOverRotations"
91
+ analyzer_method="MaxScoreOverRotations",
92
92
  )
93
93
  print(result)
94
94
 
@@ -252,7 +252,7 @@ def parse_args():
252
252
  type=check_positive,
253
253
  default=40.0,
254
254
  help="Angular sampling rate for template matching. "
255
- "A lower number yields more rotations.",
255
+ "A lower number yields more rotations. Values >= 180 sample only the identity.",
256
256
  )
257
257
  parser.add_argument(
258
258
  "-p",
@@ -380,8 +380,8 @@ def parse_args():
380
380
  type=float,
381
381
  required=False,
382
382
  default=None,
383
- help="Step size between tilts, e.g. '5'. When set a more accurate"
384
- " wedge mask will be computed.",
383
+ help="Step size between tilts, e.g. '5'. When set the wedge mask"
384
+ " reflects the individual tilts, otherwise a continuous mask is used.",
385
385
  )
386
386
  parser.add_argument(
387
387
  "--wedge_axes",
@@ -488,14 +488,16 @@ def main():
488
488
  if target_mask:
489
489
  args.target_mask = generate_tempfile_name(suffix=".mrc")
490
490
  target_mask.to_file(args.target_mask)
491
- print_block(
492
- name="Target Mask",
493
- data={
494
- "Initial Shape": initial_shape,
495
- "Sampling Rate": tuple(np.round(target_mask.sampling_rate, 2)),
496
- "Final Shape": target_mask.shape,
497
- },
498
- )
491
+
492
+ if target_mask:
493
+ print_block(
494
+ name="Target Mask",
495
+ data={
496
+ "Initial Shape": initial_shape,
497
+ "Sampling Rate": tuple(np.round(target_mask.sampling_rate, 2)),
498
+ "Final Shape": target_mask.shape,
499
+ },
500
+ )
499
501
 
500
502
  initial_shape = template.shape
501
503
  _ = crop_data(data=template, data_mask=template_mask, cutoff=args.cutoff_template)
@@ -537,15 +539,14 @@ def main():
537
539
  opening_axis, tilt_axis = [int(x) for x in args.wedge_axes.split(",")]
538
540
 
539
541
  if args.tilt_step is not None:
540
- tilt_angles = np.arange(
541
- -tilt_start, tilt_stop + args.tilt_step, args.tilt_step
542
- )
543
- angles = np.zeros((template.data.ndim, tilt_angles.size))
544
- angles[tilt_axis, :] = tilt_angles
545
- template_filter["wedge_mask"] = {
546
- "tilt_angles": angles,
542
+ template_filter["step_wedge_mask"] = {
543
+ "start_tilt": tilt_start,
544
+ "stop_tilt": tilt_stop,
545
+ "tilt_step": args.tilt_step,
547
546
  "sigma": args.wedge_smooth,
548
- "opening_axes": opening_axis,
547
+ "opening_axis": opening_axis,
548
+ "tilt_axis": tilt_axis,
549
+ "omit_negative_frequencies": True,
549
550
  }
550
551
  else:
551
552
  template_filter["continuous_wedge_mask"] = {
@@ -555,6 +556,7 @@ def main():
555
556
  "opening_axis": opening_axis,
556
557
  "infinite_plane": True,
557
558
  "sigma": args.wedge_smooth,
559
+ "omit_negative_frequencies": True,
558
560
  }
559
561
 
560
562
  if template_mask is None:
@@ -577,8 +579,9 @@ def main():
577
579
  template_mask = template_mask.rigid_transform(
578
580
  rotation_matrix=np.eye(template_mask.data.ndim),
579
581
  translation=-translation,
582
+ order=1,
580
583
  )
581
-
584
+ template_mask.origin = template.origin.copy()
582
585
  print_block(
583
586
  name="Template Mask",
584
587
  data={
@@ -590,7 +593,9 @@ def main():
590
593
  print("\n" + "-" * 80)
591
594
 
592
595
  if args.scramble_phases:
593
- template.data = scramble_phases(template.data, noise_proportion=1.0)
596
+ template.data = scramble_phases(
597
+ template.data, noise_proportion=1.0, normalize_power=True
598
+ )
594
599
 
595
600
  available_memory = backend.get_available_memory()
596
601
  if args.use_gpu:
@@ -668,7 +673,7 @@ def main():
668
673
  exit(-1)
669
674
 
670
675
  analyzer_args = {
671
- "score_threshold": 0.0,
676
+ "score_threshold": 0,
672
677
  "number_of_peaks": 1000,
673
678
  "convolution_mode": "valid",
674
679
  "use_memmap": args.use_memmap,
@@ -5,6 +5,8 @@
5
5
 
6
6
  Author: Valentin Maurer <valentin.maurer@embl-hamburg.de>
7
7
  """
8
+ from os import getcwd
9
+ from os.path import join
8
10
  import argparse
9
11
  from sys import exit
10
12
  from typing import List, Tuple
@@ -13,6 +15,7 @@ from dataclasses import dataclass
13
15
 
14
16
  import numpy as np
15
17
  from scipy.spatial.transform import Rotation
18
+ from numpy.typing import NDArray
16
19
 
17
20
  from tme import Density, Structure
18
21
  from tme.analyzer import (
@@ -26,6 +29,7 @@ from tme.matching_utils import (
26
29
  load_pickle,
27
30
  euler_to_rotationmatrix,
28
31
  euler_from_rotationmatrix,
32
+ centered_mask,
29
33
  )
30
34
 
31
35
  PEAK_CALLERS = {
@@ -52,16 +56,33 @@ def parse_args():
52
56
  help="Prefix for the output file name. Extension depends on output_format.",
53
57
  )
54
58
  parser.add_argument(
55
- "--number_of_peaks", type=int, default=1000, help="Number of peaks to consider."
59
+ "--number_of_peaks",
60
+ type=int,
61
+ default=1000,
62
+ help="Number of peaks to consider. Note, this is the number of called peaks "
63
+ ", subject to min_distance and min_boundary_distance filtering. Therefore, the "
64
+ "returned number of peaks will be at most equal to number_of_peaks. "
65
+ "Ignored when --orientations is provided.",
56
66
  )
57
67
  parser.add_argument(
58
- "--min_distance", type=int, default=5, help="Minimum distance between peaks."
68
+ "--min_distance",
69
+ type=int,
70
+ default=5,
71
+ help="Minimum distance between peaks. Ignored when --orientations is provided.",
59
72
  )
60
73
  parser.add_argument(
61
74
  "--min_boundary_distance",
62
75
  type=int,
63
76
  default=0,
64
- help="Minimum distance from target boundaries.",
77
+ help="Minimum distance from target boundaries. Ignored when --orientations "
78
+ "is provided.",
79
+ )
80
+ parser.add_argument(
81
+ "--mask_edges",
82
+ action="store_true",
83
+ default=False,
84
+ help="Whether to mask edges of the input score array according to the template shape."
85
+ "Uses twice the value of --min_boundary_distance if boht are provided.",
65
86
  )
66
87
  parser.add_argument(
67
88
  "--wedge_mask",
@@ -73,7 +94,8 @@ def parse_args():
73
94
  "--peak_caller",
74
95
  choices=list(PEAK_CALLERS.keys()),
75
96
  default="PeakCallerScipy",
76
- help="Peak caller to use for analysis. Ignored if input_file contains peaks.",
97
+ help="Peak caller to use for analysis. Ignored if input_file contains peaks or when "
98
+ "--orientations is provided.",
77
99
  )
78
100
  parser.add_argument(
79
101
  "--orientations",
@@ -205,8 +227,12 @@ class Orientations:
205
227
  return None
206
228
 
207
229
  def _to_relion_star(
208
- self, filename: str, name_prefix: str = None, ctf_image: str = None,
209
- sampling_rate : float = None, subtomogram_size : int = None
230
+ self,
231
+ filename: str,
232
+ name_prefix: str = None,
233
+ ctf_image: str = None,
234
+ sampling_rate: float = 1.0,
235
+ subtomogram_size: int = 0,
210
236
  ) -> None:
211
237
  """
212
238
  Save orientations in RELION's STAR file format.
@@ -249,12 +275,11 @@ class Orientations:
249
275
  "300.000000",
250
276
  str(int(subtomogram_size)),
251
277
  "3",
252
- str(float(sampling_rate))
278
+ str(float(sampling_rate)),
253
279
  ]
254
280
  optics_header = "\n".join(optics_header)
255
281
  optics_data = "\t".join(optics_data)
256
282
 
257
-
258
283
  header = [
259
284
  "data_particles",
260
285
  "",
@@ -283,13 +308,14 @@ class Orientations:
283
308
  _ = ofile.write("\n# version 30001\n")
284
309
  _ = ofile.write(f"{header}\n")
285
310
 
311
+ # pyTME uses a zyx data layout
286
312
  for index, (translation, rotation, score, detail) in enumerate(self):
287
313
  rotation = Rotation.from_euler("zyx", rotation, degrees=True)
288
- rotation = rotation.as_euler(seq="zyz", degrees=True)
314
+ rotation = rotation.as_euler(seq="xyx", degrees=True)
289
315
 
290
316
  translation_string = "\t".join([str(x) for x in translation][::-1])
291
- angle_string = "\t".join([str(x) for x in rotation[::-1]])
292
- name = f"{name_prefix}{index}.mrc"
317
+ angle_string = "\t".join([str(x) for x in rotation])
318
+ name = f"{name_prefix}_{index}.mrc"
293
319
  _ = ofile.write(
294
320
  f"{translation_string}\t{name}\t{angle_string}\t1{ctf_image}\n"
295
321
  )
@@ -386,11 +412,25 @@ class Orientations:
386
412
  return translation, rotation, score, detail
387
413
 
388
414
 
415
+ def load_template(filepath: str, sampling_rate: NDArray) -> "Density":
416
+ try:
417
+ template = Density.from_file(filepath)
418
+ template, _ = template.centered(0)
419
+ center_of_mass = template.center_of_mass(template.data)
420
+ except ValueError:
421
+ template = Structure.from_file(filepath)
422
+ center_of_mass = template.center_of_mass()[::-1]
423
+ template = Density.from_structure(template, sampling_rate=sampling_rate)
424
+
425
+ return template, center_of_mass
426
+
427
+
389
428
  def main():
390
429
  args = parse_args()
391
430
  data = load_pickle(args.input_file)
392
431
 
393
432
  meta = data[-1]
433
+ target_origin, _, sampling_rate, cli_args = meta
394
434
 
395
435
  if args.orientations is not None:
396
436
  orientations = Orientations.from_file(
@@ -402,13 +442,29 @@ def main():
402
442
  # Output is MaxScoreOverRotations
403
443
  if data[0].ndim == data[2].ndim:
404
444
  scores, offset, rotation_array, rotation_mapping, meta = data
445
+ if args.mask_edges:
446
+ template, center_of_mass = load_template(
447
+ cli_args.template, sampling_rate=sampling_rate
448
+ )
449
+ if not cli_args.no_centering:
450
+ template, *_ = template.centered(0)
451
+ mask_size = template.shape
452
+ if args.min_boundary_distance > 0:
453
+ mask_size = 2 * args.min_boundary_distance
454
+ scores = centered_mask(scores, np.subtract(scores.shape, mask_size) + 1)
455
+
405
456
  peak_caller = PEAK_CALLERS[args.peak_caller](
406
457
  number_of_peaks=args.number_of_peaks,
407
458
  min_distance=args.min_distance,
408
459
  min_boundary_distance=args.min_boundary_distance,
409
460
  )
410
461
  peak_caller(scores, rotation_matrix=np.eye(3))
411
- candidates = peak_caller.merge([tuple(peak_caller)])
462
+ candidates = peak_caller.merge(
463
+ candidates=[tuple(peak_caller)],
464
+ number_of_peaks=args.number_of_peaks,
465
+ min_distance=args.min_distance,
466
+ min_boundary_distance=args.min_boundary_distance,
467
+ )
412
468
  if len(candidates) == 0:
413
469
  exit(
414
470
  "Found no peaks. Try reducing min_distance or min_boundary_distance."
@@ -436,18 +492,11 @@ def main():
436
492
  orientations.to_file(filename=f"{args.output_prefix}.tsv", file_format="text")
437
493
  exit(0)
438
494
 
439
- target_origin, _, sampling_rate, cli_args = meta
440
-
441
- template_is_density, index = True, 0
442
495
  _, template_extension = splitext(cli_args.template)
443
- try:
444
- template = Density.from_file(cli_args.template)
445
- template, _ = template.centered(0)
446
- center_of_mass = template.center_of_mass(template.data)
447
- except ValueError:
448
- template_is_density = False
449
- template = Structure.from_file(cli_args.template)
450
- center_of_mass = template.center_of_mass()[::-1]
496
+ template, center_of_mass = load_template(
497
+ filepath=cli_args.template, sampling_rate=sampling_rate
498
+ )
499
+ template_is_density, index = isinstance(template, Density), 0
451
500
 
452
501
  if args.output_format == "relion":
453
502
  new_shape = np.add(template.shape, np.mod(template.shape, 2))
@@ -457,10 +506,6 @@ def main():
457
506
 
458
507
  if args.output_format in ("extraction", "relion"):
459
508
  target = Density.from_file(cli_args.target)
460
- if isinstance(template, Structure):
461
- template = Density.from_structure(
462
- template, sampling_rate=target.sampling_rate
463
- )
464
509
 
465
510
  if not np.all(np.divide(target.shape, template.shape) > 2):
466
511
  print(
@@ -489,14 +534,15 @@ def main():
489
534
  )
490
535
 
491
536
  orientations = orientations[keep_peaks]
537
+ working_directory = getcwd()
492
538
  if args.output_format == "relion":
493
539
  orientations.to_file(
494
540
  filename=f"{args.output_prefix}.star",
495
541
  file_format="relion",
496
- name_prefix=args.output_prefix,
542
+ name_prefix=join(working_directory, args.output_prefix),
497
543
  ctf_image=args.wedge_mask,
498
- sampling_rate = target.sampling_rate.max(),
499
- subtomogram_size = template.shape[0]
544
+ sampling_rate=target.sampling_rate.max(),
545
+ subtomogram_size=template.shape[0],
500
546
  )
501
547
 
502
548
  peaks = peaks[keep_peaks,]
@@ -543,7 +589,9 @@ def main():
543
589
  origin=candidate_starts[index] * sampling_rate,
544
590
  )
545
591
  # out_density.data = out_density.data * template_mask.data
546
- out_density.to_file(f"{args.output_prefix}{index}.mrc")
592
+ out_density.to_file(
593
+ join(working_directory, f"{args.output_prefix}_{index}.mrc")
594
+ )
547
595
 
548
596
  exit(0)
549
597
 
@@ -566,7 +614,10 @@ def main():
566
614
  translation=translation[::-1],
567
615
  rotation_matrix=rotation_matrix[::-1, ::-1],
568
616
  )
569
- transformed_template.to_file(f"{args.output_prefix}{index}{template_extension}")
617
+ # template_extension should contain the extension '.'
618
+ transformed_template.to_file(
619
+ f"{args.output_prefix}_{index}{template_extension}"
620
+ )
570
621
  index += 1
571
622
 
572
623