pytme 0.3b0__cp311-cp311-macosx_15_0_arm64.whl → 0.3.1__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.
Files changed (73) hide show
  1. {pytme-0.3b0.data → pytme-0.3.1.data}/scripts/estimate_memory_usage.py +1 -5
  2. {pytme-0.3b0.data → pytme-0.3.1.data}/scripts/match_template.py +177 -226
  3. {pytme-0.3b0.data → pytme-0.3.1.data}/scripts/postprocess.py +69 -47
  4. {pytme-0.3b0.data → pytme-0.3.1.data}/scripts/preprocess.py +10 -23
  5. {pytme-0.3b0.data → pytme-0.3.1.data}/scripts/preprocessor_gui.py +98 -28
  6. pytme-0.3.1.data/scripts/pytme_runner.py +1223 -0
  7. {pytme-0.3b0.dist-info → pytme-0.3.1.dist-info}/METADATA +15 -15
  8. pytme-0.3.1.dist-info/RECORD +133 -0
  9. {pytme-0.3b0.dist-info → pytme-0.3.1.dist-info}/entry_points.txt +1 -0
  10. pytme-0.3.1.dist-info/licenses/LICENSE +339 -0
  11. scripts/estimate_memory_usage.py +1 -5
  12. scripts/eval.py +93 -0
  13. scripts/extract_candidates.py +118 -99
  14. scripts/match_template.py +177 -226
  15. scripts/match_template_filters.py +1200 -0
  16. scripts/postprocess.py +69 -47
  17. scripts/preprocess.py +10 -23
  18. scripts/preprocessor_gui.py +98 -28
  19. scripts/pytme_runner.py +1223 -0
  20. scripts/refine_matches.py +156 -387
  21. tests/data/.DS_Store +0 -0
  22. tests/data/Blurring/.DS_Store +0 -0
  23. tests/data/Maps/.DS_Store +0 -0
  24. tests/data/Raw/.DS_Store +0 -0
  25. tests/data/Structures/.DS_Store +0 -0
  26. tests/preprocessing/test_frequency_filters.py +19 -10
  27. tests/preprocessing/test_utils.py +18 -0
  28. tests/test_analyzer.py +122 -122
  29. tests/test_backends.py +4 -9
  30. tests/test_density.py +0 -1
  31. tests/test_matching_cli.py +30 -30
  32. tests/test_matching_data.py +5 -5
  33. tests/test_matching_utils.py +11 -61
  34. tests/test_rotations.py +1 -1
  35. tme/__version__.py +1 -1
  36. tme/analyzer/__init__.py +1 -1
  37. tme/analyzer/_utils.py +5 -8
  38. tme/analyzer/aggregation.py +28 -9
  39. tme/analyzer/base.py +25 -36
  40. tme/analyzer/peaks.py +49 -122
  41. tme/analyzer/proxy.py +1 -0
  42. tme/backends/_jax_utils.py +31 -28
  43. tme/backends/_numpyfftw_utils.py +270 -0
  44. tme/backends/cupy_backend.py +11 -54
  45. tme/backends/jax_backend.py +72 -48
  46. tme/backends/matching_backend.py +6 -51
  47. tme/backends/mlx_backend.py +1 -27
  48. tme/backends/npfftw_backend.py +95 -90
  49. tme/backends/pytorch_backend.py +5 -26
  50. tme/density.py +7 -10
  51. tme/extensions.cpython-311-darwin.so +0 -0
  52. tme/filters/__init__.py +2 -2
  53. tme/filters/_utils.py +32 -7
  54. tme/filters/bandpass.py +225 -186
  55. tme/filters/ctf.py +138 -87
  56. tme/filters/reconstruction.py +38 -9
  57. tme/filters/wedge.py +98 -112
  58. tme/filters/whitening.py +1 -6
  59. tme/mask.py +341 -0
  60. tme/matching_data.py +20 -44
  61. tme/matching_exhaustive.py +46 -56
  62. tme/matching_optimization.py +2 -1
  63. tme/matching_scores.py +216 -412
  64. tme/matching_utils.py +82 -424
  65. tme/memory.py +1 -1
  66. tme/orientations.py +16 -8
  67. tme/parser.py +109 -29
  68. tme/preprocessor.py +2 -2
  69. tme/rotations.py +1 -1
  70. pytme-0.3b0.dist-info/RECORD +0 -122
  71. pytme-0.3b0.dist-info/licenses/LICENSE +0 -153
  72. {pytme-0.3b0.dist-info → pytme-0.3.1.dist-info}/WHEEL +0 -0
  73. {pytme-0.3b0.dist-info → pytme-0.3.1.dist-info}/top_level.txt +0 -0
scripts/postprocess.py CHANGED
@@ -9,7 +9,7 @@ import argparse
9
9
  from sys import exit
10
10
  from os import getcwd
11
11
  from typing import Tuple, List
12
- from os.path import join, splitext
12
+ from os.path import join, splitext, basename
13
13
 
14
14
  import numpy as np
15
15
  from numpy.typing import NDArray
@@ -17,7 +17,7 @@ from scipy.special import erfcinv
17
17
 
18
18
  from tme import Density, Structure, Orientations
19
19
  from tme.cli import sanitize_name, print_block, print_entry
20
- from tme.matching_utils import load_pickle, centered_mask, write_pickle
20
+ from tme.matching_utils import load_pickle, center_slice, write_pickle
21
21
  from tme.matching_optimization import create_score_object, optimize_match
22
22
  from tme.rotations import euler_to_rotationmatrix, euler_from_rotationmatrix
23
23
  from tme.analyzer import (
@@ -50,15 +50,15 @@ def parse_args():
50
50
  additional_group = parser.add_argument_group("Additional Parameters")
51
51
 
52
52
  input_group.add_argument(
53
- "--input_file",
54
- "--input_files",
53
+ "--input-file",
54
+ "--input-files",
55
55
  required=True,
56
56
  nargs="+",
57
57
  help="Path to one or multiple runs of match_template.py.",
58
58
  )
59
59
  input_group.add_argument(
60
- "--background_file",
61
- "--background_files",
60
+ "--background-file",
61
+ "--background-files",
62
62
  required=False,
63
63
  nargs="+",
64
64
  default=[],
@@ -66,7 +66,7 @@ def parse_args():
66
66
  "For instance from --scramble_phases or a different template.",
67
67
  )
68
68
  input_group.add_argument(
69
- "--target_mask",
69
+ "--target-mask",
70
70
  required=False,
71
71
  type=str,
72
72
  help="Path to an optional mask applied to template matching scores.",
@@ -81,12 +81,19 @@ def parse_args():
81
81
  )
82
82
 
83
83
  output_group.add_argument(
84
- "--output_prefix",
85
- required=True,
86
- help="Output filename, extension will be added based on output_format.",
84
+ "--output-prefix",
85
+ required=False,
86
+ default=None,
87
+ help="Output prefix. Defaults to basename of first input. Extension is "
88
+ "added with respect to chosen output format.",
89
+ )
90
+ output_group.add_argument(
91
+ "--angles-clockwise",
92
+ action="store_true",
93
+ help="Report Euler angles in clockwise format expected by RELION.",
87
94
  )
88
95
  output_group.add_argument(
89
- "--output_format",
96
+ "--output-format",
90
97
  choices=[
91
98
  "orientations",
92
99
  "relion4",
@@ -108,44 +115,44 @@ def parse_args():
108
115
  )
109
116
 
110
117
  peak_group.add_argument(
111
- "--peak_caller",
118
+ "--peak-caller",
112
119
  choices=list(PEAK_CALLERS.keys()),
113
- default="PeakCallerScipy",
120
+ default="PeakCallerMaximumFilter",
114
121
  help="Peak caller for local maxima identification.",
115
122
  )
116
123
  peak_group.add_argument(
117
- "--minimum_score",
124
+ "--min-score",
118
125
  type=float,
119
126
  default=0.0,
120
127
  help="Minimum score from which peaks will be considered.",
121
128
  )
122
129
  peak_group.add_argument(
123
- "--maximum_score",
130
+ "--max-score",
124
131
  type=float,
125
132
  default=None,
126
133
  help="Maximum score until which peaks will be considered.",
127
134
  )
128
135
  peak_group.add_argument(
129
- "--min_distance",
136
+ "--min-distance",
130
137
  type=int,
131
138
  default=5,
132
139
  help="Minimum distance between peaks.",
133
140
  )
134
141
  peak_group.add_argument(
135
- "--min_boundary_distance",
142
+ "--min-boundary-distance",
136
143
  type=int,
137
144
  default=0,
138
145
  help="Minimum distance of peaks to target edges.",
139
146
  )
140
147
  peak_group.add_argument(
141
- "--mask_edges",
148
+ "--mask-edges",
142
149
  action="store_true",
143
150
  default=False,
144
151
  help="Whether candidates should not be identified from scores that were "
145
152
  "computed from padded densities. Superseded by min_boundary_distance.",
146
153
  )
147
154
  peak_group.add_argument(
148
- "--num_peaks",
155
+ "--num-peaks",
149
156
  type=int,
150
157
  default=1000,
151
158
  required=False,
@@ -153,7 +160,7 @@ def parse_args():
153
160
  "If minimum_score is provided all peaks scoring higher will be reported.",
154
161
  )
155
162
  peak_group.add_argument(
156
- "--peak_oversampling",
163
+ "--peak-oversampling",
157
164
  type=int,
158
165
  default=1,
159
166
  help="1 / factor equals voxel precision, e.g. 2 detects half voxel "
@@ -161,33 +168,33 @@ def parse_args():
161
168
  )
162
169
 
163
170
  additional_group.add_argument(
164
- "--extraction_box_size",
171
+ "--extraction-box-size",
165
172
  type=int,
166
173
  default=None,
167
174
  help="Box size of extracted subtomograms, defaults to the centered template.",
168
175
  )
169
176
  additional_group.add_argument(
170
- "--mask_subtomograms",
177
+ "--mask-subtomograms",
171
178
  action="store_true",
172
179
  default=False,
173
180
  help="Whether to mask subtomograms using the template mask. The mask will be "
174
181
  "rotated according to determined angles.",
175
182
  )
176
183
  additional_group.add_argument(
177
- "--invert_target_contrast",
184
+ "--invert-target-contrast",
178
185
  action="store_true",
179
186
  default=False,
180
187
  help="Whether to invert the target contrast.",
181
188
  )
182
189
  additional_group.add_argument(
183
- "--n_false_positives",
190
+ "--n-false-positives",
184
191
  type=int,
185
192
  default=None,
186
193
  required=False,
187
194
  help="Number of accepted false-positives picks to determine minimum score.",
188
195
  )
189
196
  additional_group.add_argument(
190
- "--local_optimization",
197
+ "--local-optimization",
191
198
  action="store_true",
192
199
  required=False,
193
200
  help="[Experimental] Perform local optimization of candidates. Useful when the "
@@ -196,6 +203,9 @@ def parse_args():
196
203
 
197
204
  args = parser.parse_args()
198
205
 
206
+ if args.output_prefix is None:
207
+ args.output_prefix = splitext(basename(args.input_file[0]))[0]
208
+
199
209
  if args.orientations is not None:
200
210
  args.orientations = Orientations.from_file(filename=args.orientations)
201
211
 
@@ -310,7 +320,7 @@ def normalize_input(foregrounds: Tuple[str], backgrounds: Tuple[str]) -> Tuple:
310
320
 
311
321
  # We could normalize to unit sdev, but that might lead to unexpected
312
322
  # results for flat background distributions
313
- scores -= scores.mean()
323
+ # scores -= scores.mean()
314
324
  indices = tuple(slice(0, x) for x in scores.shape)
315
325
 
316
326
  indices_update = scores > scores_out[indices]
@@ -361,7 +371,8 @@ def normalize_input(foregrounds: Tuple[str], backgrounds: Tuple[str]) -> Tuple:
361
371
  data_norm = load_matching_output(background)
362
372
 
363
373
  scores = data_norm[0]
364
- scores -= scores.mean()
374
+ # scores -= scores.mean()
375
+
365
376
  indices = tuple(slice(0, x) for x in scores.shape)
366
377
  indices_update = scores > scores_norm[indices]
367
378
  scores_norm[indices][indices_update] = scores[indices_update]
@@ -370,8 +381,9 @@ def normalize_input(foregrounds: Tuple[str], backgrounds: Tuple[str]) -> Tuple:
370
381
  update = tuple(slice(0, int(x)) for x in np.minimum(out_shape, scores.shape))
371
382
  scores_out = np.full(out_shape, fill_value=0, dtype=np.float32)
372
383
  scores_out[update] = data[0][update] - scores_norm[update]
373
- scores_out[update] = np.divide(scores_out[update], 1 - scores_norm[update])
374
- np.fmax(scores_out, 0, out=scores_out)
384
+
385
+ # scores_out[update] = np.divide(scores_out[update], 1 - scores_norm[update])
386
+ scores_out = np.fmax(scores_out, 0, out=scores_out)
375
387
  data[0] = scores_out
376
388
 
377
389
  fg, bg = simple_stats(data[0]), simple_stats(scores_norm)
@@ -421,6 +433,10 @@ def main():
421
433
 
422
434
  data, entities = normalize_input(args.input_file, args.background_file)
423
435
 
436
+ if args.output_format == "pickle":
437
+ write_pickle(data, f"{args.output_prefix}.pickle")
438
+ exit(0)
439
+
424
440
  if args.target_mask:
425
441
  target_mask = Density.from_file(args.target_mask, use_memmap=True).data
426
442
  if target_mask.shape != data[0].shape:
@@ -433,11 +449,15 @@ def main():
433
449
 
434
450
  target_origin, _, sampling_rate, cli_args = data[-1]
435
451
 
452
+ # Backwards compatibility with pre v0.3.0b
453
+ if hasattr(cli_args, "no_centering"):
454
+ cli_args.centering = not cli_args.no_centering
455
+
436
456
  _, template_extension = splitext(cli_args.template)
437
457
  ret = load_template(
438
458
  filepath=cli_args.template,
439
459
  sampling_rate=sampling_rate,
440
- centering=not cli_args.no_centering,
460
+ centering=cli_args.centering,
441
461
  )
442
462
  template, center_of_mass, translation, template_is_density = ret
443
463
 
@@ -461,10 +481,6 @@ def main():
461
481
  max_shape = np.max(template.shape)
462
482
  args.min_boundary_distance = np.ceil(np.divide(max_shape, 2))
463
483
 
464
- if args.output_format == "pickle":
465
- write_pickle(data, f"{args.output_prefix}.pickle")
466
- exit(0)
467
-
468
484
  orientations = args.orientations
469
485
  if orientations is None:
470
486
  translations, rotations, scores, details = [], [], [], []
@@ -477,7 +493,10 @@ def main():
477
493
  ).astype(int)
478
494
 
479
495
  if args.min_boundary_distance > 0:
480
- scores = centered_mask(scores, new_shape=cropped_shape)
496
+ _scores = np.zeros_like(scores)
497
+ subset = center_slice(scores.shape, cropped_shape)
498
+ _scores[subset] = scores[subset]
499
+ scores = _scores
481
500
 
482
501
  if args.n_false_positives is not None:
483
502
  # Rickgauer et al. 2017
@@ -496,11 +515,11 @@ def main():
496
515
  )
497
516
  print(f"Determined minimum score cutoff: {minimum_score}.")
498
517
  minimum_score = max(minimum_score, 0)
499
- args.minimum_score = minimum_score
518
+ args.min_score = minimum_score
500
519
 
501
520
  args.batch_dims = None
502
- if hasattr(cli_args, "target_batch"):
503
- args.batch_dims = cli_args.target_batch
521
+ if hasattr(cli_args, "batch_dims"):
522
+ args.batch_dims = cli_args.batch_dims
504
523
 
505
524
  peak_caller_kwargs = {
506
525
  "shape": scores.shape,
@@ -508,8 +527,8 @@ def main():
508
527
  "min_distance": args.min_distance,
509
528
  "min_boundary_distance": args.min_boundary_distance,
510
529
  "batch_dims": args.batch_dims,
511
- "minimum_score": args.minimum_score,
512
- "maximum_score": args.maximum_score,
530
+ "min_score": args.min_score,
531
+ "max_score": args.max_score,
513
532
  }
514
533
 
515
534
  peak_caller = PEAK_CALLERS[args.peak_caller](**peak_caller_kwargs)
@@ -518,7 +537,7 @@ def main():
518
537
  state,
519
538
  scores,
520
539
  rotation_matrix=np.eye(template.data.ndim),
521
- mask=template.data,
540
+ mask=template_mask.data,
522
541
  rotation_mapping=rotation_mapping,
523
542
  rotations=rotation_array,
524
543
  )
@@ -553,12 +572,12 @@ def main():
553
572
  details=details,
554
573
  )
555
574
 
556
- if args.minimum_score is not None and len(orientations.scores):
557
- keep = orientations.scores >= args.minimum_score
575
+ if args.min_score is not None and len(orientations.scores):
576
+ keep = orientations.scores >= args.min_score
558
577
  orientations = orientations[keep]
559
578
 
560
- if args.maximum_score is not None and len(orientations.scores):
561
- keep = orientations.scores <= args.maximum_score
579
+ if args.max_score is not None and len(orientations.scores):
580
+ keep = orientations.scores <= args.max_score
562
581
  orientations = orientations[keep]
563
582
 
564
583
  if args.peak_oversampling > 1:
@@ -602,6 +621,9 @@ def main():
602
621
  orientations.rotations[index] = angles
603
622
  orientations.scores[index] = score * -1
604
623
 
624
+ if args.angles_clockwise:
625
+ orientations.rotations *= -1
626
+
605
627
  if args.output_format in ("orientations", "relion4", "relion5"):
606
628
  file_format, extension = "text", "tsv"
607
629
 
@@ -623,7 +645,7 @@ def main():
623
645
  orientations.to_file(
624
646
  filename=f"{args.output_prefix}.{extension}",
625
647
  file_format=file_format,
626
- source_path=cli_args.target,
648
+ source_path=basename(cli_args.target),
627
649
  version=version,
628
650
  )
629
651
  exit(0)
@@ -708,7 +730,7 @@ def main():
708
730
  template, center, *_ = load_template(
709
731
  filepath=cli_args.template,
710
732
  sampling_rate=sampling_rate,
711
- centering=not cli_args.no_centering,
733
+ centering=cli_args.centering,
712
734
  )
713
735
 
714
736
  for index, (translation, angles, *_) in enumerate(orientations):
scripts/preprocess.py CHANGED
@@ -11,7 +11,7 @@ import numpy as np
11
11
  from tme import Density, Structure
12
12
  from tme.cli import print_entry
13
13
  from tme.backends import backend as be
14
- from tme.filters import BandPassFilter
14
+ from tme.filters import BandPassReconstructed
15
15
 
16
16
 
17
17
  def parse_args():
@@ -24,7 +24,6 @@ def parse_args():
24
24
  io_group.add_argument(
25
25
  "-m",
26
26
  "--data",
27
- dest="data",
28
27
  type=str,
29
28
  required=True,
30
29
  help="Path to a file in PDB/MMCIF, CCP4/MRC, EM, H5 or a format supported by "
@@ -34,7 +33,6 @@ def parse_args():
34
33
  io_group.add_argument(
35
34
  "-o",
36
35
  "--output",
37
- dest="output",
38
36
  type=str,
39
37
  required=True,
40
38
  help="Path the output should be written to.",
@@ -42,22 +40,19 @@ def parse_args():
42
40
 
43
41
  box_group = parser.add_argument_group("Box")
44
42
  box_group.add_argument(
45
- "--box_size",
46
- dest="box_size",
43
+ "--box-size",
47
44
  type=int,
48
45
  required=False,
49
46
  help="Box size of the output. Defaults to twice the required box size.",
50
47
  )
51
48
  box_group.add_argument(
52
- "--sampling_rate",
53
- dest="sampling_rate",
49
+ "--sampling-rate",
54
50
  type=float,
55
51
  required=True,
56
52
  help="Sampling rate of the output file.",
57
53
  )
58
54
  box_group.add_argument(
59
- "--input_sampling_rate",
60
- dest="input_sampling_rate",
55
+ "--input-sampling-rate",
61
56
  type=float,
62
57
  required=False,
63
58
  help="Sampling rate of the input file. Defaults to header for volume "
@@ -66,15 +61,13 @@ def parse_args():
66
61
 
67
62
  modulation_group = parser.add_argument_group("Modulation")
68
63
  modulation_group.add_argument(
69
- "--invert_contrast",
70
- dest="invert_contrast",
64
+ "--invert-contrast",
71
65
  action="store_true",
72
66
  required=False,
73
67
  help="Inverts the template contrast.",
74
68
  )
75
69
  modulation_group.add_argument(
76
70
  "--lowpass",
77
- dest="lowpass",
78
71
  type=float,
79
72
  required=False,
80
73
  default=None,
@@ -82,14 +75,12 @@ def parse_args():
82
75
  "A value of 0 disables the filter.",
83
76
  )
84
77
  modulation_group.add_argument(
85
- "--no_centering",
86
- dest="no_centering",
78
+ "--no-centering",
87
79
  action="store_true",
88
80
  help="Assumes the template is already centered and omits centering.",
89
81
  )
90
82
  modulation_group.add_argument(
91
83
  "--backend",
92
- dest="backend",
93
84
  type=str,
94
85
  default=None,
95
86
  choices=be.available_backends(),
@@ -98,15 +89,13 @@ def parse_args():
98
89
 
99
90
  alignment_group = parser.add_argument_group("Modulation")
100
91
  alignment_group.add_argument(
101
- "--align_axis",
102
- dest="align_axis",
92
+ "--align-axis",
103
93
  type=int,
104
94
  required=False,
105
95
  help="Align template to given axis, e.g. 2 for z-axis.",
106
96
  )
107
97
  alignment_group.add_argument(
108
- "--align_eigenvector",
109
- dest="align_eigenvector",
98
+ "--align-eigenvector",
110
99
  type=int,
111
100
  required=False,
112
101
  default=0,
@@ -114,8 +103,7 @@ def parse_args():
114
103
  "with numerically largest eigenvalue.",
115
104
  )
116
105
  alignment_group.add_argument(
117
- "--flip_axis",
118
- dest="flip_axis",
106
+ "--flip-axis",
119
107
  action="store_true",
120
108
  required=False,
121
109
  help="Align the template to -axis instead of axis.",
@@ -185,12 +173,11 @@ def main():
185
173
  bpf_mask = 1
186
174
  lowpass = 2 * args.sampling_rate if args.lowpass is None else args.lowpass
187
175
  if args.lowpass != 0:
188
- bpf_mask = BandPassFilter(
176
+ bpf_mask = BandPassReconstructed(
189
177
  lowpass=lowpass,
190
178
  highpass=None,
191
179
  use_gaussian=True,
192
180
  return_real_fourier=True,
193
- shape_is_real_fourier=False,
194
181
  sampling_rate=data.sampling_rate,
195
182
  )(shape=data.shape)["data"]
196
183
  bpf_mask = be.to_backend_array(bpf_mask)