pytme 0.2.1__cp311-cp311-macosx_14_0_arm64.whl → 0.2.2__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.
Files changed (49) hide show
  1. {pytme-0.2.1.data → pytme-0.2.2.data}/scripts/match_template.py +147 -93
  2. {pytme-0.2.1.data → pytme-0.2.2.data}/scripts/postprocess.py +67 -26
  3. {pytme-0.2.1.data → pytme-0.2.2.data}/scripts/preprocessor_gui.py +175 -85
  4. pytme-0.2.2.dist-info/METADATA +91 -0
  5. pytme-0.2.2.dist-info/RECORD +74 -0
  6. {pytme-0.2.1.dist-info → pytme-0.2.2.dist-info}/WHEEL +1 -1
  7. scripts/extract_candidates.py +20 -13
  8. scripts/match_template.py +147 -93
  9. scripts/match_template_filters.py +154 -95
  10. scripts/postprocess.py +67 -26
  11. scripts/preprocessor_gui.py +175 -85
  12. scripts/refine_matches.py +265 -61
  13. tme/__init__.py +0 -1
  14. tme/__version__.py +1 -1
  15. tme/analyzer.py +451 -809
  16. tme/backends/__init__.py +40 -11
  17. tme/backends/_jax_utils.py +185 -0
  18. tme/backends/cupy_backend.py +111 -223
  19. tme/backends/jax_backend.py +214 -150
  20. tme/backends/matching_backend.py +445 -384
  21. tme/backends/mlx_backend.py +32 -59
  22. tme/backends/npfftw_backend.py +239 -507
  23. tme/backends/pytorch_backend.py +21 -145
  24. tme/density.py +233 -363
  25. tme/extensions.cpython-311-darwin.so +0 -0
  26. tme/matching_data.py +322 -285
  27. tme/matching_exhaustive.py +172 -1493
  28. tme/matching_optimization.py +143 -106
  29. tme/matching_scores.py +884 -0
  30. tme/matching_utils.py +280 -386
  31. tme/memory.py +377 -0
  32. tme/orientations.py +52 -12
  33. tme/parser.py +3 -4
  34. tme/preprocessing/_utils.py +61 -32
  35. tme/preprocessing/compose.py +7 -3
  36. tme/preprocessing/frequency_filters.py +49 -39
  37. tme/preprocessing/tilt_series.py +34 -40
  38. tme/preprocessor.py +560 -526
  39. tme/structure.py +491 -188
  40. tme/types.py +5 -3
  41. pytme-0.2.1.dist-info/METADATA +0 -73
  42. pytme-0.2.1.dist-info/RECORD +0 -73
  43. tme/helpers.py +0 -881
  44. tme/matching_constrained.py +0 -195
  45. {pytme-0.2.1.data → pytme-0.2.2.data}/scripts/estimate_ram_usage.py +0 -0
  46. {pytme-0.2.1.data → pytme-0.2.2.data}/scripts/preprocess.py +0 -0
  47. {pytme-0.2.1.dist-info → pytme-0.2.2.dist-info}/LICENSE +0 -0
  48. {pytme-0.2.1.dist-info → pytme-0.2.2.dist-info}/entry_points.txt +0 -0
  49. {pytme-0.2.1.dist-info → pytme-0.2.2.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,5 @@
1
1
  #!python
2
- """ Simplify picking adequate filtering and masking parameters using a GUI.
3
- Exposes tme.preprocessor.Preprocessor and tme.fitter_utils member functions
4
- to achieve this aim.
2
+ """ GUI for identifying adequate template matching filter and masks.
5
3
 
6
4
  Copyright (c) 2023 European Molecular Biology Laboratory
7
5
 
@@ -12,17 +10,20 @@ import argparse
12
10
  from typing import Tuple, Callable, List
13
11
  from typing_extensions import Annotated
14
12
 
13
+ import napari
15
14
  import numpy as np
16
15
  import pandas as pd
17
- import napari
16
+ from scipy.fft import next_fast_len
18
17
  from napari.layers import Image
19
18
  from napari.utils.events import EventedList
20
-
21
19
  from magicgui import widgets
22
20
  from qtpy.QtWidgets import QFileDialog
23
21
  from numpy.typing import NDArray
24
22
 
23
+ from tme.backends import backend
25
24
  from tme import Preprocessor, Density
25
+ from tme.preprocessing import BandPassFilter
26
+ from tme.preprocessing.tilt_series import CTF
26
27
  from tme.matching_utils import create_mask, load_pickle
27
28
 
28
29
  preprocessor = Preprocessor()
@@ -35,19 +36,57 @@ def gaussian_filter(template: NDArray, sigma: float, **kwargs: dict) -> NDArray:
35
36
 
36
37
  def bandpass_filter(
37
38
  template: NDArray,
38
- minimum_frequency: float,
39
- maximum_frequency: float,
40
- gaussian_sigma: float,
41
- **kwargs: dict,
39
+ lowpass_angstrom: float = 30,
40
+ highpass_angstrom: float = 140,
41
+ hard_edges: bool = False,
42
+ sampling_rate=None,
42
43
  ) -> NDArray:
43
- return preprocessor.bandpass_filter(
44
- template=template,
45
- minimum_frequency=minimum_frequency,
46
- maximum_frequency=maximum_frequency,
47
- sampling_rate=1,
48
- gaussian_sigma=gaussian_sigma,
49
- **kwargs,
44
+ bpf = BandPassFilter(
45
+ lowpass=lowpass_angstrom,
46
+ highpass=highpass_angstrom,
47
+ sampling_rate=np.max(sampling_rate),
48
+ use_gaussian=not hard_edges,
49
+ shape_is_real_fourier=True,
50
+ return_real_fourier=True,
50
51
  )
52
+ template_ft = np.fft.rfftn(template, s=template.shape)
53
+
54
+ mask = bpf(shape=template_ft.shape)["data"]
55
+ np.multiply(template_ft, mask, out=template_ft)
56
+ return np.fft.irfftn(template_ft, s=template.shape).real
57
+
58
+
59
+ def ctf_filter(
60
+ template: NDArray,
61
+ defocus_angstrom: float = 30000,
62
+ acceleration_voltage: float = 300,
63
+ spherical_aberration: float = 2.7,
64
+ amplitude_contrast: float = 0.07,
65
+ phase_shift: float = 0,
66
+ defocus_angle: float = 0,
67
+ sampling_rate=None,
68
+ flip_phase: bool = False,
69
+ ) -> NDArray:
70
+ fast_shape = [next_fast_len(x) for x in np.multiply(template.shape, 2)]
71
+ template_pad = backend.topleft_pad(template, fast_shape)
72
+ template_ft = np.fft.rfftn(template_pad, s=template_pad.shape)
73
+ ctf = CTF(
74
+ angles=[0],
75
+ shape=fast_shape,
76
+ defocus_x=[defocus_angstrom],
77
+ acceleration_voltage=acceleration_voltage * 1e3,
78
+ spherical_aberration=spherical_aberration * 1e7,
79
+ amplitude_contrast=amplitude_contrast,
80
+ phase_shift=[phase_shift],
81
+ defocus_angle=[defocus_angle],
82
+ sampling_rate=np.max(sampling_rate),
83
+ return_real_fourier=True,
84
+ flip_phase=flip_phase,
85
+ )
86
+ np.multiply(template_ft, ctf()["data"], out=template_ft)
87
+ template_pad = np.fft.irfftn(template_ft, s=template_pad.shape).real
88
+ template = backend.topleft_pad(template_pad, template.shape)
89
+ return template
51
90
 
52
91
 
53
92
  def difference_of_gaussian_filter(
@@ -109,61 +148,6 @@ def mean(
109
148
  return preprocessor.mean_filter(template=template, width=width)
110
149
 
111
150
 
112
- def resolution_sphere(
113
- template: NDArray,
114
- cutoff_angstrom: float,
115
- highpass: bool = False,
116
- sampling_rate=None,
117
- ) -> NDArray:
118
- if cutoff_angstrom == 0:
119
- return template
120
-
121
- cutoff_frequency = np.max(2 * sampling_rate / cutoff_angstrom)
122
-
123
- min_freq, max_freq = 0, cutoff_frequency
124
- if highpass:
125
- min_freq, max_freq = cutoff_frequency, 1e10
126
-
127
- mask = preprocessor.bandpass_mask(
128
- shape=template.shape,
129
- minimum_frequency=min_freq,
130
- maximum_frequency=max_freq,
131
- omit_negative_frequencies=False,
132
- )
133
-
134
- template_ft = np.fft.fftn(template)
135
- np.multiply(template_ft, mask, out=template_ft)
136
- return np.fft.ifftn(template_ft).real
137
-
138
-
139
- def resolution_gaussian(
140
- template: NDArray,
141
- cutoff_angstrom: float,
142
- highpass: bool = False,
143
- sampling_rate=None,
144
- ) -> NDArray:
145
- if cutoff_angstrom == 0:
146
- return template
147
-
148
- grid = preprocessor.fftfreqn(
149
- shape=template.shape, sampling_rate=sampling_rate / sampling_rate.max()
150
- )
151
-
152
- sigma_fourier = np.divide(
153
- np.max(2 * sampling_rate / cutoff_angstrom), np.sqrt(2 * np.log(2))
154
- )
155
-
156
- mask = np.exp(-(grid**2) / (2 * sigma_fourier**2))
157
- if highpass:
158
- mask = 1 - mask
159
-
160
- mask = np.fft.ifftshift(mask)
161
-
162
- template_ft = np.fft.fftn(template)
163
- np.multiply(template_ft, mask, out=template_ft)
164
- return np.fft.ifftn(template_ft).real
165
-
166
-
167
151
  def wedge(
168
152
  template: NDArray,
169
153
  tilt_start: float,
@@ -274,8 +258,7 @@ WRAPPED_FUNCTIONS = {
274
258
  "mean_filter": mean,
275
259
  "wedge_filter": wedge,
276
260
  "power_spectrum": compute_power_spectrum,
277
- "resolution_gaussian": resolution_gaussian,
278
- "resolution_sphere": resolution_sphere,
261
+ "ctf": ctf_filter,
279
262
  }
280
263
 
281
264
  EXCLUDED_FUNCTIONS = [
@@ -421,6 +404,7 @@ def sphere_mask(
421
404
  center_y: float,
422
405
  center_z: float,
423
406
  radius: float,
407
+ sigma_decay: float = 0,
424
408
  **kwargs,
425
409
  ) -> NDArray:
426
410
  return create_mask(
@@ -428,6 +412,7 @@ def sphere_mask(
428
412
  shape=template.shape,
429
413
  center=(center_x, center_y, center_z),
430
414
  radius=radius,
415
+ sigma_decay=sigma_decay,
431
416
  )
432
417
 
433
418
 
@@ -439,6 +424,7 @@ def ellipsod_mask(
439
424
  radius_x: float,
440
425
  radius_y: float,
441
426
  radius_z: float,
427
+ sigma_decay: float = 0,
442
428
  **kwargs,
443
429
  ) -> NDArray:
444
430
  return create_mask(
@@ -446,6 +432,7 @@ def ellipsod_mask(
446
432
  shape=template.shape,
447
433
  center=(center_x, center_y, center_z),
448
434
  radius=(radius_x, radius_y, radius_z),
435
+ sigma_decay=sigma_decay,
449
436
  )
450
437
 
451
438
 
@@ -457,6 +444,7 @@ def box_mask(
457
444
  height_x: int,
458
445
  height_y: int,
459
446
  height_z: int,
447
+ sigma_decay: float = 0,
460
448
  **kwargs,
461
449
  ) -> NDArray:
462
450
  return create_mask(
@@ -464,6 +452,7 @@ def box_mask(
464
452
  shape=template.shape,
465
453
  center=(center_x, center_y, center_z),
466
454
  height=(height_x, height_y, height_z),
455
+ sigma_decay=sigma_decay,
467
456
  )
468
457
 
469
458
 
@@ -476,6 +465,7 @@ def tube_mask(
476
465
  inner_radius: float,
477
466
  outer_radius: float,
478
467
  height: int,
468
+ sigma_decay: float = 0,
479
469
  **kwargs,
480
470
  ) -> NDArray:
481
471
  return create_mask(
@@ -486,6 +476,7 @@ def tube_mask(
486
476
  inner_radius=inner_radius,
487
477
  outer_radius=outer_radius,
488
478
  height=height,
479
+ sigma_decay=sigma_decay,
489
480
  )
490
481
 
491
482
 
@@ -533,13 +524,23 @@ def wedge_mask(
533
524
 
534
525
 
535
526
  def threshold_mask(
536
- template: NDArray, standard_deviation: float = 5.0, invert: bool = False, **kwargs
527
+ template: NDArray,
528
+ invert: bool = False,
529
+ standard_deviation: float = 5.0,
530
+ sigma: float = 0.0,
531
+ **kwargs,
537
532
  ) -> NDArray:
538
533
  template_mean = template.mean()
539
534
  template_deviation = standard_deviation * template.std()
540
535
  upper = template_mean + template_deviation
541
536
  lower = template_mean - template_deviation
542
- mask = np.logical_and(template > lower, template < upper)
537
+ mask = np.logical_or(template <= lower, template >= upper)
538
+
539
+ if sigma != 0:
540
+ mask_filter = preprocessor.gaussian_filter(template=mask * 1.0, sigma=sigma)
541
+ mask = np.add(mask, (1 - mask) * mask_filter)
542
+ mask[mask < np.exp(-np.square(sigma))] = 0
543
+
543
544
  if invert:
544
545
  np.invert(mask, out=mask)
545
546
 
@@ -890,6 +891,7 @@ class PointCloudWidget(widgets.Container):
890
891
 
891
892
  self.viewer = viewer
892
893
  self.dataframes = {}
894
+ self.selected_category = -1
893
895
 
894
896
  self.import_button = widgets.PushButton(
895
897
  name="Import", text="Import Point Cloud"
@@ -902,10 +904,98 @@ class PointCloudWidget(widgets.Container):
902
904
  self.export_button.clicked.connect(self._export_point_cloud)
903
905
  self.export_button.enabled = False
904
906
 
907
+ self.annotation_container = widgets.Container(name="Label", layout="horizontal")
908
+ self.positive_button = widgets.PushButton(name="Positive", text="Positive")
909
+ self.negative_button = widgets.PushButton(name="Negative", text="Negative")
910
+ self.positive_button.clicked.connect(self._set_positive)
911
+ self.negative_button.clicked.connect(self._set_negative)
912
+ self.annotation_container.append(self.positive_button)
913
+ self.annotation_container.append(self.negative_button)
914
+
915
+ self.face_color_select = widgets.ComboBox(
916
+ name="Color", choices=["Label", "Score"], value=None, nullable=True
917
+ )
918
+ self.face_color_select.changed.connect(self._update_face_color_mode)
919
+
905
920
  self.append(self.import_button)
906
921
  self.append(self.export_button)
922
+ self.append(self.annotation_container)
923
+ self.append(self.face_color_select)
924
+
907
925
  self.viewer.layers.selection.events.changed.connect(self._update_buttons)
908
926
 
927
+ self.viewer.layers.events.inserted.connect(self._initialize_points_layer)
928
+
929
+ def _update_face_color_mode(self, event: str = None):
930
+ for layer in self.viewer.layers:
931
+ if not isinstance(layer, napari.layers.Points):
932
+ continue
933
+
934
+ layer.face_color = "white"
935
+ if event == "Label":
936
+ if len(layer.properties.get("detail", ())) == 0:
937
+ continue
938
+ layer.face_color = "detail"
939
+ layer.face_color_cycle = {
940
+ -1: "grey",
941
+ 0: "red",
942
+ 1: "green",
943
+ }
944
+ layer.face_color_mode = "cycle"
945
+ elif event == "Score":
946
+ if len(layer.properties.get("score_scaled", ())) == 0:
947
+ continue
948
+ layer.face_color = "score_scaled"
949
+ layer.face_colormap = "turbo"
950
+ layer.face_color_mode = "colormap"
951
+
952
+ layer.refresh_colors()
953
+
954
+ return None
955
+
956
+ def _set_positive(self, event):
957
+ self.selected_category = 1 if self.selected_category != 1 else -1
958
+ self._update_annotation_buttons()
959
+
960
+ def _set_negative(self, event):
961
+ self.selected_category = 0 if self.selected_category != 0 else -1
962
+ self._update_annotation_buttons()
963
+
964
+ def _update_annotation_buttons(self):
965
+ selected_style = "background-color: darkgrey"
966
+ default_style = "background-color: none"
967
+
968
+ self.positive_button.native.setStyleSheet(
969
+ selected_style if self.selected_category == 1 else default_style
970
+ )
971
+ self.negative_button.native.setStyleSheet(
972
+ selected_style if self.selected_category == 0 else default_style
973
+ )
974
+
975
+ def _initialize_points_layer(self, event):
976
+ layer = event.value
977
+ if not isinstance(layer, napari.layers.Points):
978
+ return
979
+ if len(layer.properties) == 0:
980
+ layer.properties = {"detail": [-1]}
981
+
982
+ if "detail" not in layer.properties:
983
+ layer["detail"] = [-1]
984
+
985
+ layer.mouse_drag_callbacks.append(self._on_click)
986
+ return None
987
+
988
+ def _on_click(self, layer, event):
989
+ if layer.mode == "add":
990
+ layer.current_properties["detail"][-1] = self.selected_category
991
+ elif layer.mode == "select":
992
+ for index in layer.selected_data:
993
+ layer.properties["detail"][index] = self.selected_category
994
+
995
+ # TODO: Check whether current face color is the desired one already
996
+ self._update_face_color_mode(self.face_color_select.value)
997
+ layer.refresh_colors()
998
+
909
999
  def _update_buttons(self, event):
910
1000
  is_pointcloud = isinstance(
911
1001
  self.viewer.layers.selection.active, napari.layers.Points
@@ -951,9 +1041,7 @@ class PointCloudWidget(widgets.Container):
951
1041
 
952
1042
  if "score" in merged_data.columns:
953
1043
  merged_data["score"] = merged_data["score"].fillna(1)
954
- if "detail" in merged_data.columns:
955
- merged_data["detail"] = merged_data["detail"].fillna(2)
956
-
1044
+ merged_data["detail"] = layer.properties["detail"]
957
1045
  merged_data.to_csv(filename, sep="\t", index=False)
958
1046
 
959
1047
  def _get_load_path(self, event):
@@ -977,7 +1065,7 @@ class PointCloudWidget(widgets.Container):
977
1065
  dataframe["score"] = 1
978
1066
 
979
1067
  if "detail" not in dataframe.columns:
980
- dataframe["detail"] = -2
1068
+ dataframe["detail"] = -1
981
1069
 
982
1070
  point_properties = {
983
1071
  "score": np.array(dataframe["score"].values),
@@ -991,8 +1079,6 @@ class PointCloudWidget(widgets.Container):
991
1079
  points,
992
1080
  size=10,
993
1081
  properties=point_properties,
994
- face_color="score_scaled",
995
- face_colormap="turbo",
996
1082
  name=layer_name,
997
1083
  )
998
1084
  self.dataframes[layer_name] = dataframe
@@ -1025,9 +1111,14 @@ class MatchingWidget(widgets.Container):
1025
1111
  def _load_data(self, filename):
1026
1112
  data = load_pickle(filename)
1027
1113
 
1028
- _ = self.viewer.add_image(data=data[2], name="Rotations", colormap="orange")
1114
+ metadata = {"origin": data[-1][1], "sampling_rate": data[-1][2]}
1115
+ _ = self.viewer.add_image(
1116
+ data=data[2], name="Rotations", colormap="orange", metadata=metadata
1117
+ )
1029
1118
 
1030
- _ = self.viewer.add_image(data=data[0], name="Scores", colormap="turbo")
1119
+ _ = self.viewer.add_image(
1120
+ data=data[0], name="Scores", colormap="turbo", metadata=metadata
1121
+ )
1031
1122
 
1032
1123
 
1033
1124
  def main():
@@ -1045,11 +1136,10 @@ def main():
1045
1136
  widget=alignment_widget, name="Alignment", area="right"
1046
1137
  )
1047
1138
  viewer.window.add_dock_widget(widget=mask_widget, name="Mask", area="right")
1139
+ viewer.window.add_dock_widget(widget=export_widget, name="Export", area="right")
1048
1140
  viewer.window.add_dock_widget(widget=point_cloud, name="PointCloud", area="left")
1049
1141
  viewer.window.add_dock_widget(widget=matching_widget, name="Matching", area="left")
1050
1142
 
1051
- viewer.window.add_dock_widget(widget=export_widget, name="Export", area="right")
1052
-
1053
1143
  napari.run()
1054
1144
 
1055
1145
 
@@ -0,0 +1,91 @@
1
+ Metadata-Version: 2.1
2
+ Name: pytme
3
+ Version: 0.2.2
4
+ Summary: Python Template Matching Engine
5
+ Author: Valentin Maurer
6
+ Author-email: Valentin Maurer <valentin.maurer@embl-hamburg.de>
7
+ License: Proprietary
8
+ Project-URL: Homepage, https://github.com/KosinskiLab/pyTME
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.11
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: mrcfile >=1.4.3
15
+ Requires-Dist: numpy >=1.22.2
16
+ Requires-Dist: pyfftw >=0.13.1
17
+ Requires-Dist: pytest >=6.2.5
18
+ Requires-Dist: PyYAML >=6.0
19
+ Requires-Dist: scikit-image >=0.19.0
20
+ Requires-Dist: scikit-learn >=1.2.1
21
+ Requires-Dist: scipy >=1.9.1
22
+ Requires-Dist: pybind11
23
+ Requires-Dist: psutil
24
+ Requires-Dist: tifffile
25
+ Requires-Dist: h5py
26
+ Provides-Extra: all
27
+ Requires-Dist: cupy ; extra == 'all'
28
+ Requires-Dist: voltools ==0.6.0 ; extra == 'all'
29
+ Requires-Dist: torch ; extra == 'all'
30
+ Requires-Dist: jax ; extra == 'all'
31
+ Requires-Dist: jaxlib ; extra == 'all'
32
+ Provides-Extra: cupy
33
+ Requires-Dist: cupy ; extra == 'cupy'
34
+ Requires-Dist: voltools ==0.6.0 ; extra == 'cupy'
35
+ Provides-Extra: jax
36
+ Requires-Dist: jax ; extra == 'jax'
37
+ Requires-Dist: jaxlib ; extra == 'jax'
38
+ Provides-Extra: pytorch
39
+ Requires-Dist: torch ; extra == 'pytorch'
40
+
41
+ # Python Template Matching Engine (PyTME)
42
+
43
+ [![Build Status](https://img.shields.io/github/actions/workflow/status/KosinskiLab/pyTME/main.yml?label=CI)](https://github.com/KosinskiLab/pyTME/actions)
44
+ [![PyPI](https://img.shields.io/pypi/v/pytme.svg)](https://pypi.org/project/pytme/)
45
+
46
+ **[Documentation](https://kosinskilab.github.io/pyTME/)** | **[Installation](https://kosinskilab.github.io/pyTME/quickstart/installation.html)** | **[API](https://kosinskilab.github.io/pyTME/reference/index.html)**
47
+
48
+ PyTME is a Python library for data-intensive n-dimensional template matching using CPUs and GPUs.
49
+
50
+ With its [backend-agnostic design](https://kosinskilab.github.io/pyTME/reference/backends.html), the same code can be run on diverse hardware platforms using a best-of-breed approach. The underyling abstract backend specification allows for adding new backends to benefit from gains in performance and capabilities without modifying the library's core routines. The implementation of template matching scores is modular and provides developers with a flexible framework for rapid prototyping. Furthermore, pyTME supports a unique callback capability through [analyzers](https://kosinskilab.github.io/pyTME/reference/analyzer.html), which allows for injection of custom code, enabling real-time processing and manipulation of results
51
+
52
+ PyTME includes a [graphical user interface](https://kosinskilab.github.io/pyTME/quickstart/preprocessing.html#practical-example) that provides simplified mask creation, interactive filter exploration, result visualization, and manual refinement capabilities. This GUI serves as an accessible entry point to the library's core functionalities, allowing users to efficiently interact with and analyze their data.
53
+
54
+ Finally, pyTME offers specialized tools for cryogenic electron microscopy data, such as wedge masks, CTF correction, as well as [means for handling structural data](https://kosinskilab.github.io/pyTME/reference/data_structure.html). Through dedicated [integrations](https://kosinskilab.github.io/pyTME/quickstart/integrations.html), the output of pyTME seamlessly integrates with commonly used cryogenic electron microscopy software such as RELION, Dynamo and IMOD.
55
+
56
+ Running into bugs or missing a feature? Help us improve the project by opening an [issue](https://github.com/KosinskiLab/pyTME/issues).
57
+
58
+ ## Installation
59
+
60
+ We recommend installation using one of the following methods
61
+
62
+ | Method | Command |
63
+ |----------|---------------------------------------------------------|
64
+ | PyPi | `pip install pytme` |
65
+ | Source | `pip install git+https://github.com/KosinskiLab/pyTME` |
66
+ | Docker | `docker build -t pytme -f docker/Dockerfile_GPU .` |
67
+
68
+ You can find alternative installation methods in the [documentation](https://kosinskilab.github.io/pyTME/quickstart/installation.html).
69
+
70
+
71
+ ## Quickstart
72
+
73
+ Learn how to get started with
74
+
75
+ - [Preprocessing:](https://kosinskilab.github.io/pyTME/quickstart/preprocessing.html) Picking the right mask and filter for template matching.
76
+ - [Template matching:](https://kosinskilab.github.io/pyTME/quickstart/match_template.html) Find your template of interest.
77
+ - [Postprocessing](https://kosinskilab.github.io/pyTME/quickstart/postprocessing.html) Analyze template matching results and downstream integrations.
78
+
79
+ ## How to Cite
80
+
81
+ If PyTME contributed significantly to your research, please cite the corresponding publication on [SoftwareX](https://www.sciencedirect.com/science/article/pii/S2352711024000074).
82
+
83
+ ```bibtex
84
+ @article{Maurer:2024aa,
85
+ author = {Maurer, Valentin J. and Siggel, Marc and Kosinski, Jan},
86
+ journal = {SoftwareX},
87
+ pages = {101636},
88
+ title = {PyTME (Python Template Matching Engine): A fast, flexible, and multi-purpose template matching library for cryogenic electron microscopy data},
89
+ volume = {25},
90
+ year = {2024}}
91
+ ```
@@ -0,0 +1,74 @@
1
+ pytme-0.2.2.data/scripts/estimate_ram_usage.py,sha256=R1NDpFajcF-MonJ4a43SfDlA-nxBYwK7D2quzCdsVFM,2767
2
+ pytme-0.2.2.data/scripts/match_template.py,sha256=FXAzPUnKMM2_thOtTZUKtIDV7GCmhFmLXm2yJDCI6G0,41456
3
+ pytme-0.2.2.data/scripts/postprocess.py,sha256=K3tP8licTsCW1LKy8hQ8rMA7UrJ0AakM3kffx_1d4n4,24780
4
+ pytme-0.2.2.data/scripts/preprocess.py,sha256=zog-l2Je-GeouJ6SnamOMuHgTn7fFPiGnO5X03y5qSY,2527
5
+ pytme-0.2.2.data/scripts/preprocessor_gui.py,sha256=3eGC5RqnnZ3aXyfKEVstddLvKQP46kvyclj4-q01vAA,39275
6
+ scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ scripts/estimate_ram_usage.py,sha256=rN7haobnHg3YcgGJIp81FNiCzy8-saJGeEurQlmQmNQ,2768
8
+ scripts/extract_candidates.py,sha256=DAfNyuauogWvSdRWIbtH44tsk9buLn13JrL1zJjJGLE,8373
9
+ scripts/match_template.py,sha256=tEHfYXvfoBZB7bFjaBrHG3qH_e-U96fRfruB4n1uH9U,41457
10
+ scripts/match_template_filters.py,sha256=Gj4a1b_S5NWp_dfFEPFn0D7jGf-qYgBbnTvZZ4bwqOQ,42036
11
+ scripts/postprocess.py,sha256=TngTI7R8VaotxyOR9o5Hgmppjn_ILJ6ek1uwyHjkuco,24781
12
+ scripts/preprocess.py,sha256=ebJVLxbRlB6TI5YHNr0VavZ4lmaRdf8QVafyiDhh_oU,2528
13
+ scripts/preprocessor_gui.py,sha256=BUm8dsbe5LEo0FyjKrK1KdbeM75aYrS1yiCp2GfI19M,39276
14
+ scripts/refine_matches.py,sha256=Y17Ku_t0W9vglPNF2oU5EFrqoedJIm3lCGl-hXRHvjc,21920
15
+ tme/__init__.py,sha256=liARhKZ565F2Y02iWRbFzfrTCkL5You3YaUSiqUkees,202
16
+ tme/__version__.py,sha256=m6kyaNpwBcP1XYcqrelX2oS3PJuOnElOcRdBa9pEb8c,22
17
+ tme/analyzer.py,sha256=WfL6RbJDFpjwF1YiIa9NGOxEL8NddlekQIuG1r6B5fA,50325
18
+ tme/density.py,sha256=JAC2TKzD2OWBa45MCEwT6WbL1XcTdcbiErMylwIwnH0,83837
19
+ tme/extensions.cpython-311-darwin.so,sha256=e48Rshv8cWPSiBD-xqq5L8ekQJn84MqZse05fndMnkc,411984
20
+ tme/matching_data.py,sha256=QvPozAnlT9GvEUaXwqmN05j-axQ5EKuoPrNBEELMxvg,25255
21
+ tme/matching_exhaustive.py,sha256=QlFOORb2a2uDy6-wk4YU3X04y3XW4sfzwk3gC9gL27I,18843
22
+ tme/matching_memory.py,sha256=bmCAUYyXWEet-1XXhldtc0irio2ytMSsAzWYyFI5LNM,11273
23
+ tme/matching_optimization.py,sha256=Y8HfecXiOvAHXM1viBaQ_aXljqqTnGwlOlFe0MJpDRQ,45082
24
+ tme/matching_scores.py,sha256=3XxvwRNztdC-4hKdloyCfwjzt4GYzA0T-WF6YUnOcvs,30867
25
+ tme/matching_utils.py,sha256=VdQZPad9uVCQ9-32Vulh5r7bhBYylPvqv2X2DQNk91M,39736
26
+ tme/memory.py,sha256=6xeIMAncQkgYDi6w-PIYgFEWRTUPu0_OTCeRO0p9r9Q,11029
27
+ tme/orientations.py,sha256=Ul-1g2ci3QdRLfD447ZkcIo6Rv6f7gQH83JZcRkAru0,25384
28
+ tme/parser.py,sha256=fNiCAdsWI4ql92F1Ob4suiVzpjUOBlh2lad1iNY_FP8,13772
29
+ tme/preprocessor.py,sha256=ZhlP-8b4NzAzRogWOnk5DLNh_tqCMOpc4msQIJEKBTU,48566
30
+ tme/structure.py,sha256=k5kgFNHkM2mUvXS7VAwaevltLC1mUxEiDhMW0TqTNjo,65834
31
+ tme/types.py,sha256=NAY7C4qxE6yz-DXVtClMvFfoOV-spWGLNfpLATZ1LcU,442
32
+ tme/backends/__init__.py,sha256=Y69k5_Vx7VpcBNV8W0pQrebzo42lp3WBfEGAl1s4Vd4,5187
33
+ tme/backends/_jax_utils.py,sha256=v9T36Jv4ztyC38IhrbNU5tf80VKAdAXYX5ZxCWe02qU,5757
34
+ tme/backends/cupy_backend.py,sha256=rK3pjNNB4D46MqzY2DrB_GSObFJlG5GQDOt-eOwhwTg,9429
35
+ tme/backends/jax_backend.py,sha256=CUEJynVw9jqWid_vARMpNrhORgUR1SlLjz4ZTXkLuvI,9730
36
+ tme/backends/matching_backend.py,sha256=KfCOKD_rA9el3Y7BeH17KJ1apCUIIhhvn-vmbkb3CB0,33750
37
+ tme/backends/mlx_backend.py,sha256=FJhqmCzgjXAjWGX1HhHFrCy_We4YwQQBkKFNG05ctzM,7788
38
+ tme/backends/npfftw_backend.py,sha256=QAQxr3OSq9bBN_Aq0qps-nz5vvUsx0_fFn9TocI_QCw,16988
39
+ tme/backends/pytorch_backend.py,sha256=fzLucJZH5VYhrUtcX9osirNvLMsyMNPt_JNfS2l-kqg,14940
40
+ tme/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
+ tme/data/c48n309.npy,sha256=NwH64mOEbm3tStq5c98o81fY1vMOoq4nvXDAh7Z7iZg,296768
42
+ tme/data/c48n527.npy,sha256=saSUMTa1R0MisPvgFL02a7IHQSwEZ-mJu0v3qJjg5AU,506048
43
+ tme/data/c48n9.npy,sha256=bDVLV6mWjZHSQfeDc-MOCKKarfc1jaNeVvpoe2xMUy4,8768
44
+ tme/data/c48u1.npy,sha256=JeXMFzFITs2ezdc3x5lp3jo1cHHHHVADSA1Tpf77kXs,1088
45
+ tme/data/c48u1153.npy,sha256=ECiEximtYDWtIux3Fwe_EJlyn08gUqP85DN9gjkT9_k,1107008
46
+ tme/data/c48u1201.npy,sha256=aceC_Jeienz_81X4520nPpZcg5tnRhbW795EqbpWkrg,1153088
47
+ tme/data/c48u1641.npy,sha256=p4LwW3LzdTjrUUpA7H53RfNWxYfPX0XjeSwZ39Ac78Q,1575488
48
+ tme/data/c48u181.npy,sha256=mLYXrv1YHLH6DsBp5MkxHkxlxgMnj1mw_KKI0udH-FY,173888
49
+ tme/data/c48u2219.npy,sha256=p8TQeX8YHu4pdxnwJjEAlQWAPa66W7kpK96iZKZr9JE,2130368
50
+ tme/data/c48u27.npy,sha256=k03ZNEsoPwBKCy8IeIa5G0WRZqjGZMtX6Ibu7EpJHvU,26048
51
+ tme/data/c48u2947.npy,sha256=icI97ED6ct66y7FIaJAugmjzrIWk7CINCxtO3wDTnrU,2829248
52
+ tme/data/c48u3733.npy,sha256=tla-__Pf-hpN6h04vtFIfkkFdCLple11VO06kr1dXkM,3583808
53
+ tme/data/c48u4749.npy,sha256=tItOA4oV7SiqCCREwz3fyEpZoxM0lCq_jfEo5_-fp2s,4559168
54
+ tme/data/c48u5879.npy,sha256=bFk89MllIFCX_sLXTYWFquSyN1NuahH4wwnEsPJLxzA,5643968
55
+ tme/data/c48u7111.npy,sha256=CMy9kI2edH-q9eTIVdgUtXurplYNI7Uqp4dXfkkVdf8,6826688
56
+ tme/data/c48u815.npy,sha256=bCuJxLtm0Sjg3GGxtyjGzRYZ1G0Gz79XHI-71GvqQnI,782528
57
+ tme/data/c48u83.npy,sha256=7ODJYnsiuDjGbgd9GFopsyIW2IjrYI0J2X2f-cK868U,79808
58
+ tme/data/c48u8649.npy,sha256=-IPlpR4zrPQZWhhSPu4zEulFdrCEVgTMFffCB5d-huE,8303168
59
+ tme/data/c600v.npy,sha256=JqSu3ALoL1A9iguehc0YGUMFPsh2fprHHp76VXeFXIw,2528
60
+ tme/data/c600vc.npy,sha256=Yht-GFXDSjjGvsjFBvyxxEZAI-ODADPd5gEgFNZQVTA,14528
61
+ tme/data/metadata.yaml,sha256=fAgX-mEzB0QMHTEtYDG4cSMbJhYxBbDJH3sdvJvL7a8,750
62
+ tme/data/quat_to_numpy.py,sha256=-gkDZb10fKBxwfYrSLCUWvMB76TzZWELCeKsYProwws,1333
63
+ tme/preprocessing/__init__.py,sha256=7O3vDzJcIfxovJkf7avWSPtzaIVlTbmsW7egQFukC_s,98
64
+ tme/preprocessing/_utils.py,sha256=hC8Qfh_a_fk4o7v82c4-TZYYRFfo5XttcghvpkzBo0A,6193
65
+ tme/preprocessing/composable_filter.py,sha256=sp3bN8JeFB2r384cEIgvN6yXjC53GCoPhBmGI0S0kbI,781
66
+ tme/preprocessing/compose.py,sha256=tilp14UhBh98SJVJhC_STbgi7i8HeU4FB7B7KUOD8gc,1458
67
+ tme/preprocessing/frequency_filters.py,sha256=x900ntX0QI46lDdycPISO-7K0XgM0015ksCOFlm-yM4,12964
68
+ tme/preprocessing/tilt_series.py,sha256=Yb3dk7TZf7OfPWZPwVuFTirOZK4x6mjLbxYX9KtRBdg,34545
69
+ pytme-0.2.2.dist-info/LICENSE,sha256=K1IUNSVAz8BXbpH5EA8y5FpaHdvFXnAF2zeK95Lr2bY,18467
70
+ pytme-0.2.2.dist-info/METADATA,sha256=axenBTwu62Z-5jY-4kScnqv7bbWWwDU7S-VatPzSz0k,5206
71
+ pytme-0.2.2.dist-info/WHEEL,sha256=IHsX_ZtCj0uMf8jtN3eK3zfXfu1SfKRsLbDhsprnhR0,109
72
+ pytme-0.2.2.dist-info/entry_points.txt,sha256=ff3LQL3FCWfCYOwFiP9zatm7laUbnwCkuPELkQVyUO4,241
73
+ pytme-0.2.2.dist-info/top_level.txt,sha256=J8FUkazOb2fZ0n_KexnqCGyNOtie2bwisFSUBiM5-0w,12
74
+ pytme-0.2.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (72.1.0)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp311-cp311-macosx_14_0_arm64
5
5
 
@@ -108,6 +108,12 @@ def parse_args():
108
108
  type=int,
109
109
  help="Box size for extraction, defaults to two times the template.",
110
110
  )
111
+ extraction_group.add_argument(
112
+ "--translation_uncertainty",
113
+ required=False,
114
+ type=int,
115
+ help="Sets box size for extraction to template box plus this value.",
116
+ )
111
117
  extraction_group.add_argument(
112
118
  "--keep_out_of_box",
113
119
  action="store_true",
@@ -150,6 +156,9 @@ def main():
150
156
  box_size = args.box_size
151
157
  if box_size is None:
152
158
  box_size = np.multiply(template.shape, 2)
159
+ if args.translation_uncertainty is not None:
160
+ box_size = np.add(template.shape, args.translation_uncertainty)
161
+
153
162
  box_size = np.array(box_size)
154
163
  box_size = np.repeat(box_size, template.data.ndim // box_size.size).astype(int)
155
164
 
@@ -195,8 +204,6 @@ def main():
195
204
  )
196
205
  dens.data[:] = target.metadata["mean"]
197
206
 
198
- print(target.data.shape)
199
- # There appears to be an isseu with the stack creation. Trace this further
200
207
  data_subset = np.zeros(extraction_shape, dtype = target.data.dtype)
201
208
  pbar = ProgressBar(message = "Orientation ", nchars = 80, total = len(obs_slices))
202
209
  for index, (obs_slice, cand_slice) in enumerate(zip(obs_slices, cand_slices)):
@@ -219,13 +226,13 @@ def main():
219
226
  )
220
227
  target_subset.pad(box_size, center=True)
221
228
 
222
- target_value = target.data[tuple(orientations.translations[index].astype(int))]
223
- center = np.divide(target_subset.data.shape, 2).astype(int ) + np.mod(target_subset.shape, 2)
224
- print(np.where(target_subset.data == target_value), center)
225
- print(target_subset.data[tuple(center.astype(int))],
226
- target_value,
227
- target_subset.data[tuple(center.astype(int))] == target_value
228
- )
229
+ # target_value = target.data[tuple(orientations.translations[index].astype(int))]
230
+ # center = np.divide(target_subset.data.shape, 2).astype(int ) + np.mod(target_subset.shape, 2)
231
+ # print(np.where(target_subset.data == target_value), center)
232
+ # print(target_subset.data[tuple(center.astype(int))],
233
+ # target_value,
234
+ # target_subset.data[tuple(center.astype(int))] == target_value
235
+ # )
229
236
 
230
237
  dens.data[index] = target_subset.data
231
238
  print("")
@@ -241,10 +248,10 @@ def main():
241
248
  f"{splitext(args.output_file)[0]}_aligned.tsv",
242
249
  file_format="text"
243
250
  )
244
- orientations.to_file(
245
- f"{splitext(args.output_file)[0]}_aligned.star",
246
- file_format="relion"
247
- )
251
+ # orientations.to_file(
252
+ # f"{splitext(args.output_file)[0]}_aligned.star",
253
+ # file_format="relion"
254
+ # )
248
255
 
249
256
  if __name__ == "__main__":
250
257
  main()