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
tme/filters/wedge.py CHANGED
@@ -7,17 +7,17 @@ Copyright (c) 2024 European Molecular Biology Laboratory
7
7
  Author: Valentin Maurer <valentin.maurer@embl-hamburg.de>
8
8
  """
9
9
 
10
- import warnings
11
10
  from typing import Tuple, Dict
11
+ from dataclasses import dataclass
12
12
 
13
13
  import numpy as np
14
14
 
15
15
  from ..types import NDArray
16
16
  from ..backends import backend as be
17
17
  from .compose import ComposableFilter
18
- from ..matching_utils import centered
19
- from ..parser import XMLParser, StarParser
18
+ from ..matching_utils import center_slice
20
19
  from ..rotations import euler_to_rotationmatrix
20
+ from ..parser import XMLParser, StarParser, MDOCParser
21
21
  from ._utils import (
22
22
  centered_grid,
23
23
  frequency_grid_at_angle,
@@ -31,56 +31,28 @@ from ._utils import (
31
31
  __all__ = ["Wedge", "WedgeReconstructed"]
32
32
 
33
33
 
34
+ @dataclass
34
35
  class Wedge(ComposableFilter):
35
36
  """
36
37
  Generate wedge mask for tomographic data.
37
-
38
- Parameters
39
- ----------
40
- shape : tuple of int
41
- The shape of the reconstruction volume.
42
- tilt_axis : int
43
- Axis the plane is tilted over, defaults to 0 (x).
44
- opening_axis : int
45
- The projection axis, defaults to 2 (z).
46
- angles : tuple of float
47
- The tilt angles.
48
- weights : tuple of float
49
- The weights corresponding to each tilt angle.
50
- weight_type : str, optional
51
- The type of weighting to apply, defaults to None.
52
- frequency_cutoff : float, optional
53
- Frequency cutoff for created mask. Nyquist 0.5 by default.
54
-
55
- Returns
56
- -------
57
- dict
58
- data: BackendArray
59
- The filter mask.
60
- shape: tuple of ints
61
- The requested filter shape
62
- return_real_fourier: bool
63
- Whether data is compliant with rfftn.
64
- is_multiplicative_filter: bool
65
- Whether the filter is multiplicative in Fourier space.
66
38
  """
67
39
 
68
- def __init__(
69
- self,
70
- shape: Tuple[int],
71
- angles: Tuple[float],
72
- weights: Tuple[float],
73
- tilt_axis: int = 0,
74
- opening_axis: int = 2,
75
- weight_type: str = None,
76
- frequency_cutoff: float = 0.5,
77
- ):
78
- self.shape = shape
79
- self.tilt_axis = tilt_axis
80
- self.opening_axis = opening_axis
81
- self.angles = angles
82
- self.weights = weights
83
- self.frequency_cutoff = frequency_cutoff
40
+ #: The shape of the reconstruction volume.
41
+ shape: Tuple[int] = None
42
+ #: The tilt angles.
43
+ angles: Tuple[float] = None
44
+ #: The weights corresponding to each tilt angle.
45
+ weights: Tuple[float] = None
46
+ #: Axis the plane is tilted over, defaults to 0 (x).
47
+ tilt_axis: int = 0
48
+ #: The projection axis, defaults to 2 (z).
49
+ opening_axis: int = 2
50
+ #: The type of weighting to apply, defaults to None.
51
+ weight_type: str = None
52
+ #: Frequency cutoff for created mask. Nyquist 0.5 by default.
53
+ frequency_cutoff: float = 0.5
54
+ #: The sampling rate, defaults to 1 Ångstrom / voxel.
55
+ sampling_rate: Tuple[float] = 1
84
56
 
85
57
  @classmethod
86
58
  def from_file(cls, filename: str) -> "Wedge":
@@ -93,6 +65,8 @@ class Wedge(ComposableFilter):
93
65
  +-------+---------------------------------------------------------+
94
66
  | .xml | WARP/M XML file |
95
67
  +-------+---------------------------------------------------------+
68
+ | .mdoc | SerialEM file |
69
+ +-------+---------------------------------------------------------+
96
70
  | .* | Tab-separated file with optional column names |
97
71
  +-------+---------------------------------------------------------+
98
72
 
@@ -111,6 +85,8 @@ class Wedge(ComposableFilter):
111
85
  func = _from_xml
112
86
  elif filename.lower().endswith("star"):
113
87
  func = _from_star
88
+ elif filename.lower().endswith("mdoc"):
89
+ func = _from_mdoc
114
90
 
115
91
  data = func(filename)
116
92
  angles, weights = data.get("angles", None), data.get("weights", None)
@@ -132,6 +108,9 @@ class Wedge(ComposableFilter):
132
108
  )
133
109
 
134
110
  def __call__(self, **kwargs: Dict) -> NDArray:
111
+ """
112
+ Returns a Wedge stack of chosen parameters with DC component in the center.
113
+ """
135
114
  func_args = vars(self).copy()
136
115
  func_args.update(kwargs)
137
116
 
@@ -170,6 +149,7 @@ class Wedge(ComposableFilter):
170
149
  return {
171
150
  "data": ret,
172
151
  "shape": func_args["shape"],
152
+ "return_real_fourier": func_args.get("return_real_fourier", False),
173
153
  "is_multiplicative_filter": True,
174
154
  }
175
155
 
@@ -196,7 +176,12 @@ class Wedge(ComposableFilter):
196
176
  return wedges
197
177
 
198
178
  def weight_relion(
199
- self, shape: Tuple[int], opening_axis: int, tilt_axis: int, **kwargs
179
+ self,
180
+ shape: Tuple[int],
181
+ opening_axis: int,
182
+ tilt_axis: int,
183
+ sampling_rate: float = 1.0,
184
+ **kwargs,
200
185
  ) -> NDArray:
201
186
  """
202
187
  Generate weighted wedges based on the RELION 1.4 formalism, weighting each
@@ -211,7 +196,6 @@ class Wedge(ComposableFilter):
211
196
  tilt_shape = compute_tilt_shape(
212
197
  shape=shape, opening_axis=opening_axis, reduce_dim=True
213
198
  )
214
-
215
199
  wedges = np.zeros((len(self.angles), *tilt_shape))
216
200
  for index, angle in enumerate(self.angles):
217
201
  frequency_grid = frequency_grid_at_angle(
@@ -219,15 +203,14 @@ class Wedge(ComposableFilter):
219
203
  opening_axis=opening_axis,
220
204
  tilt_axis=tilt_axis,
221
205
  angle=angle,
222
- sampling_rate=1,
206
+ sampling_rate=sampling_rate,
223
207
  )
224
208
  sigma = np.sqrt(self.weights[index] * 4 / (8 * np.pi**2))
225
209
  sigma = -2 * np.pi**2 * sigma**2
226
- np.square(frequency_grid, out=frequency_grid)
227
- np.multiply(sigma, frequency_grid, out=frequency_grid)
228
- np.exp(frequency_grid, out=frequency_grid)
229
- np.multiply(frequency_grid, np.cos(np.radians(angle)), out=frequency_grid)
230
- wedges[index] = frequency_grid
210
+ frequency_grid = np.square(frequency_grid, out=frequency_grid)
211
+ frequency_grid = np.multiply(sigma, frequency_grid, out=frequency_grid)
212
+ frequency_grid = np.exp(frequency_grid, out=frequency_grid)
213
+ wedges[index] = np.multiply(frequency_grid, np.cos(np.radians(angle)))
231
214
 
232
215
  return wedges
233
216
 
@@ -239,6 +222,7 @@ class Wedge(ComposableFilter):
239
222
  amplitude: float = 0.245,
240
223
  power: float = -1.665,
241
224
  offset: float = 2.81,
225
+ sampling_rate: float = 1.0,
242
226
  **kwargs,
243
227
  ) -> NDArray:
244
228
  """
@@ -264,7 +248,7 @@ class Wedge(ComposableFilter):
264
248
  opening_axis=opening_axis,
265
249
  tilt_axis=tilt_axis,
266
250
  angle=angle,
267
- sampling_rate=1,
251
+ sampling_rate=sampling_rate,
268
252
  )
269
253
 
270
254
  with np.errstate(divide="ignore"):
@@ -283,55 +267,35 @@ class Wedge(ComposableFilter):
283
267
  return wedges
284
268
 
285
269
 
270
+ @dataclass
286
271
  class WedgeReconstructed:
287
272
  """
288
273
  Initialize :py:class:`WedgeReconstructed`.
289
-
290
- Parameters
291
- ----------
292
- angles :tuple of float, optional
293
- The tilt angles, defaults to None.
294
- tilt_axis : int
295
- Axis the plane is tilted over, defaults to 0 (x).
296
- opening_axis : int
297
- The projection axis, defaults to 2 (z).
298
- weights : tuple of float, optional
299
- Weights to assign to individual wedge components.
300
- weight_wedge : bool, optional
301
- Whether individual wedge components should be weighted. If True and weights
302
- is None, uses the cosine of the angle otherwise weights.
303
- create_continuous_wedge: bool, optional
304
- Whether to create a continous wedge or a per-component wedge. Weights are only
305
- considered for non-continuous wedges.
306
- frequency_cutoff : float, optional
307
- Filter window applied during reconstruction.
308
- **kwargs : Dict
309
- Additional keyword arguments.
310
274
  """
311
275
 
312
- def __init__(
313
- self,
314
- opening_axis: int = 2,
315
- tilt_axis: int = 0,
316
- angles: Tuple[float] = None,
317
- weights: Tuple[float] = None,
318
- weight_wedge: bool = False,
319
- create_continuous_wedge: bool = False,
320
- frequency_cutoff: float = 0.5,
321
- reconstruction_filter: str = None,
322
- **kwargs: Dict,
323
- ):
324
- self.angles = angles
325
- self.opening_axis = opening_axis
326
- self.tilt_axis = tilt_axis
327
- self.weights = weights
328
- self.weight_wedge = weight_wedge
329
- self.reconstruction_filter = reconstruction_filter
330
- self.create_continuous_wedge = create_continuous_wedge
331
- self.frequency_cutoff = frequency_cutoff
276
+ #: The tilt angles, defaults to None.
277
+ angles: Tuple[float] = None
278
+ #: Weights to assign to individual wedge components. Not considered for continuous wedge
279
+ weights: Tuple[float] = None
280
+ #: Whether individual wedge components should be weighted.
281
+ weight_wedge: bool = False
282
+ #: Whether to create a continous wedge or a per-component wedge.
283
+ create_continuous_wedge: bool = False
284
+ #: Frequency cutoff of filter
285
+ frequency_cutoff: float = 0.5
286
+ #: Axis the plane is tilted over, defaults to 0 (x).
287
+ tilt_axis: int = 0
288
+ #: The projection axis, defaults to 2 (z).
289
+ opening_axis: int = 2
290
+ #: Filter window applied during reconstruction.
291
+ reconstruction_filter: str = None
292
+
293
+ def __post_init__(self):
294
+ if self.create_continuous_wedge:
295
+ self.angles = (min(self.angles), max(self.angles))
332
296
 
333
297
  def __call__(
334
- self, shape: Tuple[int], return_real_fourier: bool = False, **kwargs: Dict
298
+ self, shape: Tuple[int], return_real_fourier: bool = False, **kwargs
335
299
  ) -> Dict:
336
300
  """
337
301
  Generate the reconstructed wedge.
@@ -341,10 +305,8 @@ class WedgeReconstructed:
341
305
  shape : tuple of int
342
306
  The shape of the reconstruction volume.
343
307
  return_real_fourier : tuple of int
344
- Return a shape compliant with rfft, i.e., omit the negative frequencies
345
- terms resulting in a return shape (*shape[:-1], shape[-1]//2+1). Defaults
346
- to False.
347
- **kwargs : Dict
308
+ Return a shape compliant with rfftn. Defaults to False.
309
+ **kwargs : dict
348
310
  Additional keyword arguments.
349
311
 
350
312
  Returns
@@ -373,7 +335,6 @@ class WedgeReconstructed:
373
335
  )
374
336
 
375
337
  ret = func(shape=shape, **func_args)
376
-
377
338
  frequency_cutoff = func_args.get("frequency_cutoff", None)
378
339
  if frequency_cutoff is not None:
379
340
  frequency_mask = fftfreqn(
@@ -528,7 +489,11 @@ class WedgeReconstructed:
528
489
  )
529
490
  wedge_volume += plane_rotated * weights[index]
530
491
 
531
- wedge_volume = centered(wedge_volume, (shape[opening_axis], shape[tilt_axis]))
492
+ subset = center_slice(
493
+ wedge_volume.shape, (shape[opening_axis], shape[tilt_axis])
494
+ )
495
+ wedge_volume = wedge_volume[subset]
496
+
532
497
  np.fmin(wedge_volume, np.max(weights), wedge_volume)
533
498
 
534
499
  if opening_axis > tilt_axis:
@@ -565,7 +530,7 @@ def _from_xml(filename: str, **kwargs) -> Dict:
565
530
 
566
531
  def _from_star(filename: str, **kwargs) -> Dict:
567
532
  """
568
- Read tilt data from a tomostar STAR file.
533
+ Read tilt data from a STAR file.
569
534
 
570
535
  Parameters
571
536
  ----------
@@ -577,8 +542,33 @@ def _from_star(filename: str, **kwargs) -> Dict:
577
542
  Dict
578
543
  A dictionary with one key for each column.
579
544
  """
580
- data = StarParser(filename, delimiter=None)["data_"]
581
- return {"angles": data["_wrpAxisAngle"], "weights": data["_wrpDose"]}
545
+ data = StarParser(filename, delimiter=None)
546
+ if "data_stopgap_wedgelist" in data:
547
+ angles = data["data_stopgap_wedgelist"]["_tilt_angle"]
548
+ weights = data["data_stopgap_wedgelist"]["_exposure"]
549
+ else:
550
+ angles = data["data_"]["_wrpAxisAngle"]
551
+ weights = data["data_"]["_wrpDose"]
552
+ return {"angles": angles, "weights": weights}
553
+
554
+
555
+ def _from_mdoc(filename: str, **kwargs) -> Dict:
556
+ """
557
+ Read tilt data from a SerialEM MDOC file.
558
+
559
+ Parameters
560
+ ----------
561
+ filename : str
562
+ The path to the text file.
563
+
564
+ Returns
565
+ -------
566
+ Dict
567
+ A dictionary with one key for each column.
568
+ """
569
+ data = MDOCParser(filename)
570
+ cumulative_exposure = np.multiply(np.add(1, data["ZValue"]), data["ExposureDose"])
571
+ return {"angles": data["TiltAngle"], "weights": cumulative_exposure}
582
572
 
583
573
 
584
574
  def _from_text(filename: str, **kwargs) -> Dict:
@@ -604,10 +594,6 @@ def _from_text(filename: str, **kwargs) -> Dict:
604
594
  if "angles" in data[0]:
605
595
  headers = data.pop(0)
606
596
  else:
607
- warnings.warn(
608
- f"Did not find a column named 'angles' in {filename}. Assuming "
609
- "first column specifies angles."
610
- )
611
597
  if len(data[0]) != 1:
612
598
  raise ValueError(
613
599
  "Found more than one column without column names. Please add "
tme/filters/whitening.py CHANGED
@@ -24,11 +24,6 @@ class LinearWhiteningFilter(ComposableFilter):
24
24
  """
25
25
  Compute Fourier power spectrums and perform whitening.
26
26
 
27
- Parameters
28
- ----------
29
- **kwargs : Dict, optional
30
- Additional keyword arguments.
31
-
32
27
  References
33
28
  ----------
34
29
  .. [1] de Teresa-Trueba, I.; Goetz, S. K.; Mattausch, A.; Stojanovska, F.; Zimmerli, C. E.;
@@ -39,7 +34,7 @@ class LinearWhiteningFilter(ComposableFilter):
39
34
  13375 (2023)
40
35
  """
41
36
 
42
- def __init__(self, **kwargs):
37
+ def __init__(self, *args, **kwargs):
43
38
  pass
44
39
 
45
40
  @staticmethod