pytme 0.2.1__cp311-cp311-macosx_14_0_arm64.whl → 0.2.3__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 (52) hide show
  1. {pytme-0.2.1.data → pytme-0.2.3.data}/scripts/match_template.py +219 -216
  2. {pytme-0.2.1.data → pytme-0.2.3.data}/scripts/postprocess.py +86 -54
  3. pytme-0.2.3.data/scripts/preprocess.py +132 -0
  4. {pytme-0.2.1.data → pytme-0.2.3.data}/scripts/preprocessor_gui.py +181 -94
  5. pytme-0.2.3.dist-info/METADATA +92 -0
  6. pytme-0.2.3.dist-info/RECORD +75 -0
  7. {pytme-0.2.1.dist-info → pytme-0.2.3.dist-info}/WHEEL +1 -1
  8. pytme-0.2.1.data/scripts/preprocess.py → scripts/eval.py +1 -1
  9. scripts/extract_candidates.py +20 -13
  10. scripts/match_template.py +219 -216
  11. scripts/match_template_filters.py +154 -95
  12. scripts/postprocess.py +86 -54
  13. scripts/preprocess.py +95 -56
  14. scripts/preprocessor_gui.py +181 -94
  15. scripts/refine_matches.py +265 -61
  16. tme/__init__.py +0 -1
  17. tme/__version__.py +1 -1
  18. tme/analyzer.py +458 -813
  19. tme/backends/__init__.py +40 -11
  20. tme/backends/_jax_utils.py +187 -0
  21. tme/backends/cupy_backend.py +109 -226
  22. tme/backends/jax_backend.py +230 -152
  23. tme/backends/matching_backend.py +445 -384
  24. tme/backends/mlx_backend.py +32 -59
  25. tme/backends/npfftw_backend.py +240 -507
  26. tme/backends/pytorch_backend.py +30 -151
  27. tme/density.py +248 -371
  28. tme/extensions.cpython-311-darwin.so +0 -0
  29. tme/matching_data.py +328 -284
  30. tme/matching_exhaustive.py +195 -1499
  31. tme/matching_optimization.py +143 -106
  32. tme/matching_scores.py +887 -0
  33. tme/matching_utils.py +287 -388
  34. tme/memory.py +377 -0
  35. tme/orientations.py +78 -21
  36. tme/parser.py +3 -4
  37. tme/preprocessing/_utils.py +61 -32
  38. tme/preprocessing/composable_filter.py +7 -4
  39. tme/preprocessing/compose.py +7 -3
  40. tme/preprocessing/frequency_filters.py +49 -39
  41. tme/preprocessing/tilt_series.py +44 -72
  42. tme/preprocessor.py +560 -526
  43. tme/structure.py +491 -188
  44. tme/types.py +5 -3
  45. pytme-0.2.1.dist-info/METADATA +0 -73
  46. pytme-0.2.1.dist-info/RECORD +0 -73
  47. tme/helpers.py +0 -881
  48. tme/matching_constrained.py +0 -195
  49. {pytme-0.2.1.data → pytme-0.2.3.data}/scripts/estimate_ram_usage.py +0 -0
  50. {pytme-0.2.1.dist-info → pytme-0.2.3.dist-info}/LICENSE +0 -0
  51. {pytme-0.2.1.dist-info → pytme-0.2.3.dist-info}/entry_points.txt +0 -0
  52. {pytme-0.2.1.dist-info → pytme-0.2.3.dist-info}/top_level.txt +0 -0
tme/memory.py ADDED
@@ -0,0 +1,377 @@
1
+ """ Compute memory consumption of template matching components.
2
+
3
+ Copyright (c) 2023 European Molecular Biology Laboratory
4
+
5
+ Author: Valentin Maurer <valentin.maurer@embl-hamburg.de>
6
+ """
7
+
8
+ from abc import ABC, abstractmethod
9
+ from typing import Tuple
10
+
11
+ import numpy as np
12
+ from pyfftw import next_fast_len
13
+
14
+
15
+ class MatchingMemoryUsage(ABC):
16
+ """
17
+ Class specification for estimating the memory requirements of template matching.
18
+
19
+ Parameters
20
+ ----------
21
+ fast_shape : tuple of int
22
+ Shape of the real array.
23
+ ft_shape : tuple of int
24
+ Shape of the complex array.
25
+ float_nbytes : int
26
+ Number of bytes of the used float, e.g. 4 for float32.
27
+ complex_nbytes : int
28
+ Number of bytes of the used complex, e.g. 8 for complex64.
29
+ integer_nbytes : int
30
+ Number of bytes of the used integer, e.g. 4 for int32.
31
+
32
+ Attributes
33
+ ----------
34
+ real_array_size : int
35
+ Number of elements in real array.
36
+ complex_array_size : int
37
+ Number of elements in complex array.
38
+ float_nbytes : int
39
+ Number of bytes of the used float, e.g. 4 for float32.
40
+ complex_nbytes : int
41
+ Number of bytes of the used complex, e.g. 8 for complex64.
42
+ integer_nbytes : int
43
+ Number of bytes of the used integer, e.g. 4 for int32.
44
+
45
+ Methods
46
+ -------
47
+ base_usage():
48
+ Returns the base memory usage in bytes.
49
+ per_fork():
50
+ Returns the memory usage in bytes per fork.
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ fast_shape: Tuple[int],
56
+ ft_shape: Tuple[int],
57
+ float_nbytes: int,
58
+ complex_nbytes: int,
59
+ integer_nbytes: int,
60
+ ):
61
+ self.real_array_size = np.prod(fast_shape)
62
+ self.complex_array_size = np.prod(ft_shape)
63
+ self.float_nbytes = float_nbytes
64
+ self.complex_nbytes = complex_nbytes
65
+ self.integer_nbytes = integer_nbytes
66
+
67
+ @abstractmethod
68
+ def base_usage(self) -> int:
69
+ """Return the base memory usage in bytes."""
70
+
71
+ @abstractmethod
72
+ def per_fork(self) -> int:
73
+ """Return the memory usage per fork in bytes."""
74
+
75
+
76
+ class CCMemoryUsage(MatchingMemoryUsage):
77
+ """
78
+ Memory usage estimation for CC scoring.
79
+
80
+ See Also
81
+ --------
82
+ :py:meth:`tme.matching_exhaustive.cc_setup`.
83
+ """
84
+
85
+ def base_usage(self) -> int:
86
+ float_arrays = self.real_array_size * self.float_nbytes
87
+ complex_arrays = self.complex_array_size * self.complex_nbytes
88
+ return float_arrays + complex_arrays
89
+
90
+ def per_fork(self) -> int:
91
+ float_arrays = self.real_array_size * self.float_nbytes
92
+ complex_arrays = self.complex_array_size * self.complex_nbytes
93
+ return float_arrays + complex_arrays
94
+
95
+
96
+ class LCCMemoryUsage(CCMemoryUsage):
97
+ """
98
+ Memory usage estimation for LCC scoring.
99
+ See Also
100
+ --------
101
+ :py:meth:`tme.matching_exhaustive.lcc_setup`.
102
+ """
103
+
104
+
105
+ class CORRMemoryUsage(MatchingMemoryUsage):
106
+ """
107
+ Memory usage estimation for CORR scoring.
108
+
109
+ See Also
110
+ --------
111
+ :py:meth:`tme.matching_exhaustive.corr_setup`.
112
+ """
113
+
114
+ def base_usage(self) -> int:
115
+ float_arrays = self.real_array_size * self.float_nbytes * 4
116
+ complex_arrays = self.complex_array_size * self.complex_nbytes
117
+ return float_arrays + complex_arrays
118
+
119
+ def per_fork(self) -> int:
120
+ float_arrays = self.real_array_size * self.float_nbytes
121
+ complex_arrays = self.complex_array_size * self.complex_nbytes
122
+ return float_arrays + complex_arrays
123
+
124
+
125
+ class CAMMemoryUsage(CORRMemoryUsage):
126
+ """
127
+ Memory usage estimation for CAM scoring.
128
+
129
+ See Also
130
+ --------
131
+ :py:meth:`tme.matching_exhaustive.cam_setup`.
132
+ """
133
+
134
+
135
+ class FLCSphericalMaskMemoryUsage(CORRMemoryUsage):
136
+ """
137
+ Memory usage estimation for FLCMSphericalMask scoring.
138
+
139
+ See Also
140
+ --------
141
+ :py:meth:`tme.matching_exhaustive.flcSphericalMask_setup`.
142
+ """
143
+
144
+
145
+ class FLCMemoryUsage(MatchingMemoryUsage):
146
+ """
147
+ Memory usage estimation for FLC scoring.
148
+
149
+ See Also
150
+ --------
151
+ :py:meth:`tme.matching_exhaustive.flc_setup`.
152
+ """
153
+
154
+ def base_usage(self) -> int:
155
+ float_arrays = self.real_array_size * self.float_nbytes * 2
156
+ complex_arrays = self.complex_array_size * self.complex_nbytes * 2
157
+ return float_arrays + complex_arrays
158
+
159
+ def per_fork(self) -> int:
160
+ float_arrays = self.real_array_size * self.float_nbytes * 3
161
+ complex_arrays = self.complex_array_size * self.complex_nbytes * 2
162
+ return float_arrays + complex_arrays
163
+
164
+
165
+ class MCCMemoryUsage(MatchingMemoryUsage):
166
+ """
167
+ Memory usage estimation for MCC scoring.
168
+
169
+ See Also
170
+ --------
171
+ :py:meth:`tme.matching_exhaustive.mcc_setup`.
172
+ """
173
+
174
+ def base_usage(self) -> int:
175
+ float_arrays = self.real_array_size * self.float_nbytes * 2
176
+ complex_arrays = self.complex_array_size * self.complex_nbytes * 3
177
+ return float_arrays + complex_arrays
178
+
179
+ def per_fork(self) -> int:
180
+ float_arrays = self.real_array_size * self.float_nbytes * 6
181
+ complex_arrays = self.complex_array_size * self.complex_nbytes
182
+ return float_arrays + complex_arrays
183
+
184
+
185
+ class MaxScoreOverRotationsMemoryUsage(MatchingMemoryUsage):
186
+ """
187
+ Memory usage estimation MaxScoreOverRotations Analyzer.
188
+
189
+ See Also
190
+ --------
191
+ :py:class:`tme.analyzer.MaxScoreOverRotations`.
192
+ """
193
+
194
+ def base_usage(self) -> int:
195
+ float_arrays = self.real_array_size * self.float_nbytes * 2
196
+ return float_arrays
197
+
198
+ def per_fork(self) -> int:
199
+ return 0
200
+
201
+
202
+ class PeakCallerMaximumFilterMemoryUsage(MatchingMemoryUsage):
203
+ """
204
+ Memory usage estimation MaxScoreOverRotations Analyzer.
205
+
206
+ See Also
207
+ --------
208
+ :py:class:`tme.analyzer.PeakCallerMaximumFilter`.
209
+ """
210
+
211
+ def base_usage(self) -> int:
212
+ float_arrays = self.real_array_size * self.float_nbytes
213
+ return float_arrays
214
+
215
+ def per_fork(self) -> int:
216
+ float_arrays = self.real_array_size * self.float_nbytes
217
+ return float_arrays
218
+
219
+
220
+ class CupyBackendMemoryUsage(MatchingMemoryUsage):
221
+ """
222
+ Memory usage estimation for CupyBackend.
223
+
224
+ See Also
225
+ --------
226
+ :py:class:`tme.backends.CupyBackend`.
227
+ """
228
+
229
+ def base_usage(self) -> int:
230
+ # FFT plans, overhead from assigning FFT result, rotation interpolation
231
+ complex_arrays = self.real_array_size * self.complex_nbytes * 3
232
+ float_arrays = self.complex_array_size * self.float_nbytes * 2
233
+ return float_arrays + complex_arrays
234
+
235
+ def per_fork(self) -> int:
236
+ return 0
237
+
238
+
239
+ def _compute_convolution_shapes(
240
+ arr1_shape: Tuple[int], arr2_shape: Tuple[int]
241
+ ) -> Tuple[Tuple[int], Tuple[int], Tuple[int]]:
242
+ """
243
+ Computes regular, optimized and fourier convolution shape.
244
+
245
+ Parameters
246
+ ----------
247
+ arr1_shape : tuple
248
+ Tuple of integers corresponding to array1 shape.
249
+ arr2_shape : tuple
250
+ Tuple of integers corresponding to array2 shape.
251
+
252
+ Returns
253
+ -------
254
+ tuple
255
+ Tuple with regular convolution shape, convolution shape optimized for faster
256
+ fourier transform, shape of the forward fourier transform
257
+ (see :py:meth:`build_fft`).
258
+ """
259
+ convolution_shape = np.add(arr1_shape, arr2_shape) - 1
260
+ fast_shape = [next_fast_len(x) for x in convolution_shape]
261
+ fast_ft_shape = list(fast_shape[:-1]) + [fast_shape[-1] // 2 + 1]
262
+
263
+ return convolution_shape, fast_shape, fast_ft_shape
264
+
265
+
266
+ MATCHING_MEMORY_REGISTRY = {
267
+ "CC": CCMemoryUsage,
268
+ "LCC": LCCMemoryUsage,
269
+ "CORR": CORRMemoryUsage,
270
+ "CAM": CAMMemoryUsage,
271
+ "MCC": MCCMemoryUsage,
272
+ "FLCSphericalMask": FLCSphericalMaskMemoryUsage,
273
+ "FLC": FLCMemoryUsage,
274
+ "MaxScoreOverRotations": MaxScoreOverRotationsMemoryUsage,
275
+ "PeakCallerMaximumFilter": PeakCallerMaximumFilterMemoryUsage,
276
+ "cupy": CupyBackendMemoryUsage,
277
+ "pytorch": CupyBackendMemoryUsage,
278
+ }
279
+
280
+
281
+ def estimate_ram_usage(
282
+ shape1: Tuple[int],
283
+ shape2: Tuple[int],
284
+ matching_method: str,
285
+ ncores: int,
286
+ analyzer_method: str = None,
287
+ backend: str = None,
288
+ float_nbytes: int = 4,
289
+ complex_nbytes: int = 8,
290
+ integer_nbytes: int = 4,
291
+ ) -> int:
292
+ """
293
+ Estimate the RAM usage for a given convolution operation based on input shapes,
294
+ matching_method, and number of cores.
295
+
296
+ Parameters
297
+ ----------
298
+ shape1 : tuple
299
+ The shape of the input target.
300
+ shape2 : tuple
301
+ The shape of the input template.
302
+ matching_method : str
303
+ The method used for the operation.
304
+ is_gpu : bool, optional
305
+ Whether the computation is performed on GPU. This factors in FFT
306
+ plan caching.
307
+ analyzer_method : str, optional
308
+ The method used for score analysis.
309
+ backend : str, optional
310
+ Backend used for computation.
311
+ ncores : int
312
+ The number of CPU cores used for the operation.
313
+ float_nbytes : int
314
+ Number of bytes of the used float, e.g. 4 for float32.
315
+ complex_nbytes : int
316
+ Number of bytes of the used complex, e.g. 8 for complex64.
317
+ integer_nbytes : int
318
+ Number of bytes of the used integer, e.g. 4 for int32.
319
+
320
+ Returns
321
+ -------
322
+ int
323
+ The estimated RAM usage for the operation in bytes.
324
+
325
+ Notes
326
+ -----
327
+ Residual memory from other objects that may remain allocated during
328
+ template matching, e.g. the full sized target when using splitting,
329
+ are not considered by this function.
330
+
331
+ Raises
332
+ ------
333
+ ValueError
334
+ If an unsupported matching_methode is provided.
335
+ """
336
+ if matching_method not in MATCHING_MEMORY_REGISTRY:
337
+ raise ValueError(
338
+ f"Supported options are {','.join(MATCHING_MEMORY_REGISTRY.keys())}"
339
+ )
340
+
341
+ convolution_shape, fast_shape, ft_shape = _compute_convolution_shapes(
342
+ shape1, shape2
343
+ )
344
+
345
+ memory_instance = MATCHING_MEMORY_REGISTRY[matching_method](
346
+ fast_shape=fast_shape,
347
+ ft_shape=ft_shape,
348
+ float_nbytes=float_nbytes,
349
+ complex_nbytes=complex_nbytes,
350
+ integer_nbytes=integer_nbytes,
351
+ )
352
+
353
+ nbytes = memory_instance.base_usage() + memory_instance.per_fork() * ncores
354
+
355
+ analyzer_instance = MATCHING_MEMORY_REGISTRY.get(analyzer_method, None)
356
+ if analyzer_instance is not None:
357
+ analyzer_instance = analyzer_instance(
358
+ fast_shape=fast_shape,
359
+ ft_shape=ft_shape,
360
+ float_nbytes=float_nbytes,
361
+ complex_nbytes=complex_nbytes,
362
+ integer_nbytes=integer_nbytes,
363
+ )
364
+ nbytes += analyzer_instance.base_usage() + analyzer_instance.per_fork() * ncores
365
+
366
+ backend_instance = MATCHING_MEMORY_REGISTRY.get(backend, None)
367
+ if backend_instance is not None:
368
+ backend_instance = backend_instance(
369
+ fast_shape=fast_shape,
370
+ ft_shape=ft_shape,
371
+ float_nbytes=float_nbytes,
372
+ complex_nbytes=complex_nbytes,
373
+ integer_nbytes=integer_nbytes,
374
+ )
375
+ nbytes += backend_instance.base_usage() + backend_instance.per_fork() * ncores
376
+
377
+ return nbytes
tme/orientations.py CHANGED
@@ -6,6 +6,7 @@
6
6
  Author: Valentin Maurer <valentin.maurer@embl-hamburg.de>
7
7
  """
8
8
  import re
9
+ import warnings
9
10
  from collections import deque
10
11
  from dataclasses import dataclass
11
12
  from string import ascii_lowercase
@@ -62,16 +63,16 @@ class Orientations:
62
63
  Array with additional orientation details (n, ).
63
64
  """
64
65
 
65
- #: Return a numpy array with translations of each orientation (n x d).
66
+ #: Array with translations of each orientation (n, d).
66
67
  translations: np.ndarray
67
68
 
68
- #: Return a numpy array with euler angles of each orientation in zxy format (n x d).
69
+ #: Array with zyx euler angles of each orientation (n, d).
69
70
  rotations: np.ndarray
70
71
 
71
- #: Return a numpy array with the score of each orientation (n, ).
72
+ #: Array with scores of each orientation (n, ).
72
73
  scores: np.ndarray
73
74
 
74
- #: Return a numpy array with additional orientation details (n, ).
75
+ #: Array with additional details of each orientation(n, ).
75
76
  details: np.ndarray
76
77
 
77
78
  def __post_init__(self):
@@ -130,9 +131,21 @@ class Orientations:
130
131
  "scores",
131
132
  "details",
132
133
  )
133
- kwargs = {attr: getattr(self, attr)[indices] for attr in attributes}
134
+ kwargs = {attr: getattr(self, attr)[indices].copy() for attr in attributes}
134
135
  return self.__class__(**kwargs)
135
136
 
137
+ def copy(self) -> "Orientations":
138
+ """
139
+ Create a copy of the current class instance.
140
+
141
+ Returns
142
+ -------
143
+ :py:class:`Orientations`
144
+ Copy of the class instance.
145
+ """
146
+ indices = np.arange(self.scores.size)
147
+ return self[indices]
148
+
136
149
  def to_file(self, filename: str, file_format: type = None, **kwargs) -> None:
137
150
  """
138
151
  Save the current class instance to a file in the specified format.
@@ -146,7 +159,7 @@ class Orientations:
146
159
  the file_format from the typical extension. Supported formats are
147
160
 
148
161
  +---------------+----------------------------------------------------+
149
- | text | pyTME's standard tab-separated orientations file |
162
+ | text | pytme's standard tab-separated orientations file |
150
163
  +---------------+----------------------------------------------------+
151
164
  | relion | Creates a STAR file of orientations |
152
165
  +---------------+----------------------------------------------------+
@@ -207,11 +220,11 @@ class Orientations:
207
220
  with open(filename, mode="w", encoding="utf-8") as ofile:
208
221
  _ = ofile.write(f"{header}\n")
209
222
  for translation, angles, score, detail in self:
210
- translation_string = "\t".join([str(x) for x in translation])
211
- angle_string = "\t".join([str(x) for x in angles])
212
- _ = ofile.write(
213
- f"{translation_string}\t{angle_string}\t{score}\t{detail}\n"
223
+ out_string = (
224
+ "\t".join([str(x) for x in (*translation, *angles, score, detail)])
225
+ + "\n"
214
226
  )
227
+ _ = ofile.write(out_string)
215
228
  return None
216
229
 
217
230
  def _to_dynamo_tbl(
@@ -289,7 +302,7 @@ class Orientations:
289
302
  def _to_relion_star(
290
303
  self,
291
304
  filename: str,
292
- name_prefix: str = None,
305
+ name: str = None,
293
306
  ctf_image: str = None,
294
307
  sampling_rate: float = 1.0,
295
308
  subtomogram_size: int = 0,
@@ -301,8 +314,9 @@ class Orientations:
301
314
  ----------
302
315
  filename : str
303
316
  The name of the file to save the orientations.
304
- name_prefix : str, optional
305
- A prefix to add to the image names in the STAR file.
317
+ name : str or list of str, optional
318
+ Path to image file the orientation is in reference to. If name is a list
319
+ its assumed to correspond to _rlnImageName, otherwise _rlnMicrographName.
306
320
  ctf_image : str, optional
307
321
  Path to CTF or wedge mask RELION.
308
322
  sampling_rate : float, optional
@@ -340,6 +354,21 @@ class Orientations:
340
354
  optics_header = "\n".join(optics_header)
341
355
  optics_data = "\t".join(optics_data)
342
356
 
357
+ if name is None:
358
+ name = ""
359
+ warnings.warn(
360
+ "Consider specifying the name argument. A single string will be "
361
+ "interpreted as path to the original micrograph, a list of strings "
362
+ "as path to individual subsets."
363
+ )
364
+
365
+ name_reference = "_rlnImageName"
366
+ if isinstance(name, str):
367
+ name = [
368
+ name,
369
+ ] * self.translations.shape[0]
370
+ name_reference = "_rlnMicrographName"
371
+
343
372
  header = [
344
373
  "data_particles",
345
374
  "",
@@ -347,7 +376,7 @@ class Orientations:
347
376
  "_rlnCoordinateX",
348
377
  "_rlnCoordinateY",
349
378
  "_rlnCoordinateZ",
350
- "_rlnImageName",
379
+ name_reference,
351
380
  "_rlnAngleRot",
352
381
  "_rlnAngleTilt",
353
382
  "_rlnAnglePsi",
@@ -359,8 +388,6 @@ class Orientations:
359
388
  ctf_image = "" if ctf_image is None else f"\t{ctf_image}"
360
389
 
361
390
  header = "\n".join(header)
362
- name_prefix = "" if name_prefix is None else name_prefix
363
-
364
391
  with open(filename, mode="w", encoding="utf-8") as ofile:
365
392
  _ = ofile.write(f"{optics_header}\n")
366
393
  _ = ofile.write(f"{optics_data}\n")
@@ -375,9 +402,8 @@ class Orientations:
375
402
 
376
403
  translation_string = "\t".join([str(x) for x in translation][::-1])
377
404
  angle_string = "\t".join([str(x) for x in rotation])
378
- name = f"{name_prefix}_{index}.mrc"
379
405
  _ = ofile.write(
380
- f"{translation_string}\t{name}\t{angle_string}\t1{ctf_image}\n"
406
+ f"{translation_string}\t{name[index]}\t{angle_string}\t1{ctf_image}\n"
381
407
  )
382
408
 
383
409
  return None
@@ -465,8 +491,10 @@ class Orientations:
465
491
 
466
492
  Notes
467
493
  -----
468
- The text file is expected to have a header and data in columns corresponding to
469
- z, y, x, euler_z, euler_y, euler_x, score, detail.
494
+ The text file is expected to have a header and data in columns. Colums containing
495
+ the name euler are considered to specify rotations. The second last and last
496
+ column correspond to score and detail. Its possible to only specify translations,
497
+ in this case the remaining columns will be filled with trivial values.
470
498
  """
471
499
  with open(filename, mode="r", encoding="utf-8") as infile:
472
500
  data = [x.strip().split("\t") for x in infile.read().split("\n")]
@@ -493,6 +521,32 @@ class Orientations:
493
521
  score = np.array(score)
494
522
  detail = np.array(detail)
495
523
 
524
+ if translation.shape[1] == len(header):
525
+ rotation = np.zeros(translation.shape, dtype=np.float32)
526
+ score = np.zeros(translation.shape[0], dtype=np.float32)
527
+ detail = np.zeros(translation.shape[0], dtype=np.float32) - 1
528
+
529
+ if rotation.size == 0 and translation.shape[0] != 0:
530
+ rotation = np.zeros(translation.shape, dtype=np.float32)
531
+
532
+ header_order = tuple(x for x in header if x in ascii_lowercase)
533
+ header_order = zip(header_order, range(len(header_order)))
534
+ sort_order = tuple(
535
+ x[1] for x in sorted(header_order, key=lambda x: x[0], reverse=True)
536
+ )
537
+ translation = translation[..., sort_order]
538
+
539
+ header_order = tuple(
540
+ x
541
+ for x in header
542
+ if "euler" in x and x.replace("euler_", "") in ascii_lowercase
543
+ )
544
+ header_order = zip(header_order, range(len(header_order)))
545
+ sort_order = tuple(
546
+ x[1] for x in sorted(header_order, key=lambda x: x[0], reverse=True)
547
+ )
548
+ rotation = rotation[..., sort_order]
549
+
496
550
  return translation, rotation, score, detail
497
551
 
498
552
  @staticmethod
@@ -544,7 +598,10 @@ class Orientations:
544
598
  cls, filename: str, delimiter: str = None
545
599
  ) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
546
600
  ret = cls._parse_star(filename=filename, delimiter=delimiter)
547
- ret = ret["data_particles"]
601
+
602
+ ret = ret.get("data_particles", None)
603
+ if ret is None:
604
+ raise ValueError(f"No data_particles section found in {filename}.")
548
605
 
549
606
  translation = np.vstack(
550
607
  (ret["_rlnCoordinateZ"], ret["_rlnCoordinateY"], ret["_rlnCoordinateX"])
tme/parser.py CHANGED
@@ -137,8 +137,7 @@ class Parser(ABC):
137
137
 
138
138
  class PDBParser(Parser):
139
139
  """
140
- A Parser subclass for converting PDB file data into a dictionary representation.
141
- This class is specifically designed to work with PDB file format.
140
+ Convert PDB file data into a dictionary representation [1]_.
142
141
 
143
142
  References
144
143
  ----------
@@ -228,8 +227,8 @@ class PDBParser(Parser):
228
227
 
229
228
  class MMCIFParser(Parser):
230
229
  """
231
- A Parser subclass for converting MMCIF file data into a dictionary representation.
232
- This implementation heavily relies on the atomium library [1]_.
230
+ Convert MMCIF file data into a dictionary representation. This implementation
231
+ heavily relies on the atomium library [1]_.
233
232
 
234
233
  References
235
234
  ----------