pytme 0.1.8__cp311-cp311-macosx_14_0_arm64.whl → 0.2.0b0__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 (36) hide show
  1. {pytme-0.1.8.data → pytme-0.2.0b0.data}/scripts/match_template.py +148 -126
  2. pytme-0.2.0b0.data/scripts/postprocess.py +570 -0
  3. {pytme-0.1.8.data → pytme-0.2.0b0.data}/scripts/preprocessor_gui.py +244 -60
  4. {pytme-0.1.8.dist-info → pytme-0.2.0b0.dist-info}/METADATA +3 -1
  5. pytme-0.2.0b0.dist-info/RECORD +66 -0
  6. {pytme-0.1.8.dist-info → pytme-0.2.0b0.dist-info}/WHEEL +1 -1
  7. scripts/extract_candidates.py +218 -0
  8. scripts/match_template.py +148 -126
  9. scripts/match_template_filters.py +852 -0
  10. scripts/postprocess.py +380 -435
  11. scripts/preprocessor_gui.py +244 -60
  12. scripts/refine_matches.py +218 -0
  13. tme/__init__.py +2 -1
  14. tme/__version__.py +1 -1
  15. tme/analyzer.py +545 -78
  16. tme/backends/cupy_backend.py +80 -15
  17. tme/backends/npfftw_backend.py +33 -2
  18. tme/backends/pytorch_backend.py +15 -7
  19. tme/density.py +156 -63
  20. tme/extensions.cpython-311-darwin.so +0 -0
  21. tme/matching_constrained.py +195 -0
  22. tme/matching_data.py +76 -32
  23. tme/matching_exhaustive.py +366 -204
  24. tme/matching_memory.py +1 -0
  25. tme/matching_optimization.py +728 -651
  26. tme/matching_utils.py +152 -8
  27. tme/orientations.py +561 -0
  28. tme/preprocessor.py +21 -18
  29. tme/structure.py +2 -37
  30. pytme-0.1.8.data/scripts/postprocess.py +0 -625
  31. pytme-0.1.8.dist-info/RECORD +0 -61
  32. {pytme-0.1.8.data → pytme-0.2.0b0.data}/scripts/estimate_ram_usage.py +0 -0
  33. {pytme-0.1.8.data → pytme-0.2.0b0.data}/scripts/preprocess.py +0 -0
  34. {pytme-0.1.8.dist-info → pytme-0.2.0b0.dist-info}/LICENSE +0 -0
  35. {pytme-0.1.8.dist-info → pytme-0.2.0b0.dist-info}/entry_points.txt +0 -0
  36. {pytme-0.1.8.dist-info → pytme-0.2.0b0.dist-info}/top_level.txt +0 -0
tme/orientations.py ADDED
@@ -0,0 +1,561 @@
1
+ #!python3
2
+ """ Handle template matching peaks and convert between formats.
3
+
4
+ Copyright (c) 2024 European Molecular Biology Laboratory
5
+
6
+ Author: Valentin Maurer <valentin.maurer@embl-hamburg.de>
7
+ """
8
+ import re
9
+ from collections import deque
10
+ from dataclasses import dataclass
11
+ from typing import List, Tuple, Dict
12
+
13
+ import numpy as np
14
+ from scipy.spatial.transform import Rotation
15
+
16
+
17
+ @dataclass
18
+ class Orientations:
19
+ """
20
+ Handle template matching peaks and convert between formats.
21
+ """
22
+
23
+ #: Return a numpy array with translations of each orientation (n x d).
24
+ translations: np.ndarray
25
+
26
+ #: Return a numpy array with euler angles of each orientation in zxy format (n x d).
27
+ rotations: np.ndarray
28
+
29
+ #: Return a numpy array with the score of each orientation (n, ).
30
+ scores: np.ndarray
31
+
32
+ #: Return a numpy array with additional orientation details (n, ).
33
+ details: np.ndarray
34
+
35
+ def __iter__(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
36
+ """
37
+ Iterate over the current class instance. Each iteration returns a orientation
38
+ defined by its translation, rotation, score and additional detail.
39
+
40
+ Yields
41
+ ------
42
+ Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]
43
+ A tuple of arrays defining the given orientation.
44
+ """
45
+ yield from zip(self.translations, self.rotations, self.scores, self.details)
46
+
47
+ def __getitem__(self, indices: List[int]) -> "Orientations":
48
+ """
49
+ Retrieve a subset of orientations based on the provided indices.
50
+
51
+ Parameters
52
+ ----------
53
+ indices : List[int]
54
+ A list of indices specifying the orientations to be retrieved.
55
+
56
+ Returns
57
+ -------
58
+ :py:class:`Orientations`
59
+ A new :py:class:`Orientations`instance containing only the selected orientations.
60
+ """
61
+ indices = np.asarray(indices)
62
+ attributes = (
63
+ "translations",
64
+ "rotations",
65
+ "scores",
66
+ "details",
67
+ )
68
+ kwargs = {attr: getattr(self, attr)[indices] for attr in attributes}
69
+ return self.__class__(**kwargs)
70
+
71
+ def to_file(self, filename: str, file_format: type = None, **kwargs) -> None:
72
+ """
73
+ Save the current class instance to a file in the specified format.
74
+
75
+ Parameters
76
+ ----------
77
+ filename : str
78
+ The name of the file where the orientations will be saved.
79
+ file_format : type, optional
80
+ The format in which to save the orientations. Supported formats are 'text' and 'relion'.
81
+ **kwargs : dict
82
+ Additional keyword arguments specific to the file format.
83
+
84
+ Raises
85
+ ------
86
+ ValueError
87
+ If an unsupported file format is specified.
88
+ """
89
+ mapping = {
90
+ "text": self._to_text,
91
+ "relion": self._to_relion_star,
92
+ "dynamo": self._to_dynamo_tbl,
93
+ }
94
+ if file_format is None:
95
+ file_format = "text"
96
+ if filename.lower().endswith(".star"):
97
+ file_format = "relion"
98
+ elif filename.lower().endswith(".tbl"):
99
+ file_format = "dynamo"
100
+
101
+ func = mapping.get(file_format, None)
102
+ if func is None:
103
+ raise ValueError(
104
+ f"{file_format} not implemented. Supported are {','.join(mapping.keys())}."
105
+ )
106
+
107
+ return func(filename=filename, **kwargs)
108
+
109
+ def _to_text(self, filename: str) -> None:
110
+ """
111
+ Save orientations in a text file format.
112
+
113
+ Parameters
114
+ ----------
115
+ filename : str
116
+ The name of the file to save the orientations.
117
+
118
+ Notes
119
+ -----
120
+ The file is saved with a header specifying each column: z, y, x, euler_z,
121
+ euler_y, euler_x, score, detail. Each row in the file corresponds to an orientation.
122
+ """
123
+ header = "\t".join(
124
+ ["z", "y", "x", "euler_z", "euler_y", "euler_x", "score", "detail"]
125
+ )
126
+ with open(filename, mode="w", encoding="utf-8") as ofile:
127
+ _ = ofile.write(f"{header}\n")
128
+ for translation, angles, score, detail in self:
129
+ translation_string = "\t".join([str(x) for x in translation])
130
+ angle_string = "\t".join([str(x) for x in angles])
131
+ _ = ofile.write(
132
+ f"{translation_string}\t{angle_string}\t{score}\t{detail}\n"
133
+ )
134
+ return None
135
+
136
+ def _to_dynamo_tbl(
137
+ self,
138
+ filename: str,
139
+ name_prefix: str = None,
140
+ sampling_rate: float = 1.0,
141
+ subtomogram_size: int = 0,
142
+ ) -> None:
143
+ """
144
+ Save orientations in Dynamo's tbl file format.
145
+
146
+ Parameters
147
+ ----------
148
+ filename : str
149
+ The name of the file to save the orientations.
150
+ sampling_rate : float, optional
151
+ Subtomogram sampling rate in angstrom per voxel
152
+
153
+ Notes
154
+ -----
155
+ The file is saved with a standard header used in Dynamo tbl files
156
+ outlined in [1]_. Each row corresponds to a particular partice.
157
+
158
+ References
159
+ ----------
160
+ .. [1] https://wiki.dynamo.biozentrum.unibas.ch/w/index.php/Table
161
+
162
+ The file is saved with a standard header used in Dynamo STAR files.
163
+ Each row in the file corresponds to an orientation.
164
+ """
165
+ with open(filename, mode="w", encoding="utf-8") as ofile:
166
+ for index, (translation, rotation, score, detail) in enumerate(self):
167
+ rotation = Rotation.from_euler("zyx", rotation, degrees=True)
168
+ rotation = rotation.as_euler(seq="xyx", degrees=True)
169
+ out = [
170
+ index,
171
+ 1,
172
+ 0,
173
+ 0,
174
+ 0,
175
+ 0,
176
+ *rotation,
177
+ self.scores[index],
178
+ self.scores[index],
179
+ 0,
180
+ 0,
181
+ # Wedge parameters
182
+ -90,
183
+ 90,
184
+ -60,
185
+ 60,
186
+ 0,
187
+ 0,
188
+ 0,
189
+ 0,
190
+ 0,
191
+ 0,
192
+ # Coordinate in original volume
193
+ *translation[::-1],
194
+ 0,
195
+ 0,
196
+ 0,
197
+ 0,
198
+ 0,
199
+ 0,
200
+ 0,
201
+ 0,
202
+ sampling_rate,
203
+ 3,
204
+ 0,
205
+ 0,
206
+ ]
207
+ _ = ofile.write(" ".join([str(x) for x in out]) + "\n")
208
+
209
+ return None
210
+
211
+ def _to_relion_star(
212
+ self,
213
+ filename: str,
214
+ name_prefix: str = None,
215
+ ctf_image: str = None,
216
+ sampling_rate: float = 1.0,
217
+ subtomogram_size: int = 0,
218
+ ) -> None:
219
+ """
220
+ Save orientations in RELION's STAR file format.
221
+
222
+ Parameters
223
+ ----------
224
+ filename : str
225
+ The name of the file to save the orientations.
226
+ name_prefix : str, optional
227
+ A prefix to add to the image names in the STAR file.
228
+ ctf_image : str, optional
229
+ Path to CTF or wedge mask RELION.
230
+ sampling_rate : float, optional
231
+ Subtomogram sampling rate in angstrom per voxel
232
+ subtomogram_size : int, optional
233
+ Size of the square shaped subtomogram.
234
+
235
+ Notes
236
+ -----
237
+ The file is saved with a standard header used in RELION STAR files.
238
+ Each row in the file corresponds to an orientation.
239
+ """
240
+ optics_header = [
241
+ "# version 30001",
242
+ "data_optics",
243
+ "",
244
+ "loop_",
245
+ "_rlnOpticsGroup",
246
+ "_rlnOpticsGroupName",
247
+ "_rlnSphericalAberration",
248
+ "_rlnVoltage",
249
+ "_rlnImageSize",
250
+ "_rlnImageDimensionality",
251
+ "_rlnImagePixelSize",
252
+ ]
253
+ optics_data = [
254
+ "1",
255
+ "opticsGroup1",
256
+ "2.700000",
257
+ "300.000000",
258
+ str(int(subtomogram_size)),
259
+ "3",
260
+ str(float(sampling_rate)),
261
+ ]
262
+ optics_header = "\n".join(optics_header)
263
+ optics_data = "\t".join(optics_data)
264
+
265
+ header = [
266
+ "data_particles",
267
+ "",
268
+ "loop_",
269
+ "_rlnCoordinateX",
270
+ "_rlnCoordinateY",
271
+ "_rlnCoordinateZ",
272
+ "_rlnImageName",
273
+ "_rlnAngleRot",
274
+ "_rlnAngleTilt",
275
+ "_rlnAnglePsi",
276
+ "_rlnOpticsGroup",
277
+ ]
278
+ if ctf_image is not None:
279
+ header.append("_rlnCtfImage")
280
+
281
+ ctf_image = "" if ctf_image is None else f"\t{ctf_image}"
282
+
283
+ header = "\n".join(header)
284
+ name_prefix = "" if name_prefix is None else name_prefix
285
+
286
+ with open(filename, mode="w", encoding="utf-8") as ofile:
287
+ _ = ofile.write(f"{optics_header}\n")
288
+ _ = ofile.write(f"{optics_data}\n")
289
+
290
+ _ = ofile.write("\n# version 30001\n")
291
+ _ = ofile.write(f"{header}\n")
292
+
293
+ # pyTME uses a zyx data layout
294
+ for index, (translation, rotation, score, detail) in enumerate(self):
295
+ rotation = Rotation.from_euler("zyx", rotation, degrees=True)
296
+ rotation = rotation.as_euler(seq="xyx", degrees=True)
297
+
298
+ translation_string = "\t".join([str(x) for x in translation][::-1])
299
+ angle_string = "\t".join([str(x) for x in rotation])
300
+ name = f"{name_prefix}_{index}.mrc"
301
+ _ = ofile.write(
302
+ f"{translation_string}\t{name}\t{angle_string}\t1{ctf_image}\n"
303
+ )
304
+
305
+ return None
306
+
307
+ @classmethod
308
+ def from_file(
309
+ cls, filename: str, file_format: type = None, **kwargs
310
+ ) -> "Orientations":
311
+ """
312
+ Create an instance of :py:class:`Orientations` from a file.
313
+
314
+ Parameters
315
+ ----------
316
+ filename : str
317
+ The name of the file from which to read the orientations.
318
+ file_format : type, optional
319
+ The format of the file. Currently, only 'text' format is supported.
320
+ **kwargs : dict
321
+ Additional keyword arguments specific to the file format.
322
+
323
+ Returns
324
+ -------
325
+ :py:class:`Orientations`
326
+ An instance of :py:class:`Orientations` populated with data from the file.
327
+
328
+ Raises
329
+ ------
330
+ ValueError
331
+ If an unsupported file format is specified.
332
+ """
333
+ mapping = {"text": cls._from_text, "relion": cls._from_relion_star}
334
+ if file_format is None:
335
+ file_format = "text"
336
+ if filename.lower().endswith(".star"):
337
+ file_format = "relion"
338
+
339
+ func = mapping.get(file_format, None)
340
+ if func is None:
341
+ raise ValueError(
342
+ f"{file_format} not implemented. Supported are {','.join(mapping.keys())}."
343
+ )
344
+
345
+ translations, rotations, scores, details, *_ = func(filename=filename, **kwargs)
346
+ return cls(
347
+ translations=translations,
348
+ rotations=rotations,
349
+ scores=scores,
350
+ details=details,
351
+ )
352
+
353
+ @staticmethod
354
+ def _from_text(
355
+ filename: str,
356
+ ) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
357
+ """
358
+ Read orientations from a text file.
359
+
360
+ Parameters
361
+ ----------
362
+ filename : str
363
+ The name of the file from which to read the orientations.
364
+
365
+ Returns
366
+ -------
367
+ Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]
368
+ A tuple containing numpy arrays for translations, rotations, scores,
369
+ and details.
370
+
371
+ Notes
372
+ -----
373
+ The text file is expected to have a header and data in columns corresponding to
374
+ z, y, x, euler_z, euler_y, euler_x, score, detail.
375
+ """
376
+ with open(filename, mode="r", encoding="utf-8") as infile:
377
+ data = [x.strip().split("\t") for x in infile.read().split("\n")]
378
+ _ = data.pop(0)
379
+
380
+ translation, rotation, score, detail = [], [], [], []
381
+ for candidate in data:
382
+ if len(candidate) <= 1:
383
+ continue
384
+ if len(candidate) != 8:
385
+ candidate.append(-1)
386
+
387
+ candidate = [float(x) for x in candidate]
388
+ translation.append((candidate[0], candidate[1], candidate[2]))
389
+ rotation.append((candidate[3], candidate[4], candidate[5]))
390
+ score.append(candidate[6])
391
+ detail.append(candidate[7])
392
+
393
+ translation = np.vstack(translation).astype(int)
394
+ rotation = np.vstack(rotation).astype(float)
395
+ score = np.array(score).astype(float)
396
+ detail = np.array(detail).astype(float)
397
+
398
+ return translation, rotation, score, detail
399
+
400
+ @staticmethod
401
+ def _parse_star(filename: str, delimiter: str = None) -> Dict:
402
+ pattern = re.compile(r"\s*#.*")
403
+ with open(filename, mode="r", encoding="utf-8") as infile:
404
+ data = infile.read()
405
+
406
+ data = deque(filter(lambda line: line and line[0] != "#", data.split("\n")))
407
+
408
+ ret, category, block = {}, None, []
409
+ while data:
410
+ line = data.popleft()
411
+
412
+ if line.startswith("data") and not line.startswith("_"):
413
+ if category != line and category is not None:
414
+ headers = list(ret[category].keys())
415
+ headers = [pattern.sub("", x) for x in headers]
416
+ ret[category] = {
417
+ header: list(column)
418
+ for header, column in zip(headers, zip(*block))
419
+ }
420
+ block.clear()
421
+ category = line
422
+ if category not in ret:
423
+ ret[category] = {}
424
+ continue
425
+
426
+ if line.startswith("_"):
427
+ ret[category][line] = []
428
+ continue
429
+
430
+ if line.startswith("loop"):
431
+ continue
432
+
433
+ line_split = line.split(delimiter)
434
+ if len(line_split):
435
+ block.append(line_split)
436
+
437
+ headers = list(ret[category].keys())
438
+ headers = [pattern.sub("", x) for x in headers]
439
+ ret[category] = {
440
+ header: list(column) for header, column in zip(headers, zip(*block))
441
+ }
442
+ return ret
443
+
444
+ @classmethod
445
+ def _from_relion_star(
446
+ cls, filename: str, delimiter: str = None
447
+ ) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
448
+ ret = cls._parse_star(filename=filename, delimiter=delimiter)
449
+ ret = ret["data_particles"]
450
+
451
+ translation = (
452
+ np.vstack(
453
+ (ret["_rlnCoordinateZ"], ret["_rlnCoordinateY"], ret["_rlnCoordinateX"])
454
+ )
455
+ .astype(np.float32)
456
+ .astype(int)
457
+ .T
458
+ )
459
+
460
+ rotation = (
461
+ np.vstack((ret["_rlnAngleRot"], ret["_rlnAngleTilt"], ret["_rlnAnglePsi"]))
462
+ .astype(np.float32)
463
+ .T
464
+ )
465
+
466
+ rotation = Rotation.from_euler("xyx", rotation, degrees=True)
467
+ rotation = rotation.as_euler(seq="zyx", degrees=True)
468
+ score = np.ones(translation.shape[0])
469
+ detail = np.ones(translation.shape[0]) * 1
470
+
471
+ return translation, rotation, score, detail
472
+
473
+ def get_extraction_slices(
474
+ self,
475
+ target_shape: Tuple[int],
476
+ extraction_shape: Tuple[int],
477
+ drop_out_of_box: bool = False,
478
+ return_orientations: bool = False,
479
+ ) -> "Orientations":
480
+ """
481
+ Calculate slices for extracting regions of interest within a larger array.
482
+
483
+ Parameters
484
+ ----------
485
+ target_shape : Tuple[int]
486
+ The shape of the target array within which regions are to be extracted.
487
+ extraction_shape : Tuple[int]
488
+ The shape of the regions to be extracted.
489
+ drop_out_of_box : bool, optional
490
+ If True, drop regions that extend beyond the target array boundary, by default False.
491
+ return_orientations : bool, optional
492
+ If True, return orientations along with slices, by default False.
493
+
494
+ Returns
495
+ -------
496
+ Union[Tuple[List[slice]], Tuple["Orientations", List[slice], List[slice]]]
497
+ If return_orientations is False, returns a tuple containing slices for candidate
498
+ regions and observation regions.
499
+ If return_orientations is True, returns a tuple containing orientations along
500
+ with slices for candidate regions and observation regions.
501
+
502
+ Raises
503
+ ------
504
+ SystemExit
505
+ If no peak remains after filtering, indicating an error.
506
+ """
507
+ left_pad = np.divide(extraction_shape, 2).astype(int)
508
+ right_pad = np.add(left_pad, np.mod(extraction_shape, 2)).astype(int)
509
+
510
+ obs_start = np.subtract(self.translations, left_pad)
511
+ obs_stop = np.add(self.translations, right_pad)
512
+
513
+ cand_start = np.subtract(np.maximum(obs_start, 0), obs_start)
514
+ cand_stop = np.subtract(obs_stop, np.minimum(obs_stop, target_shape))
515
+ cand_stop = np.subtract(extraction_shape, cand_stop)
516
+ obs_start = np.maximum(obs_start, 0)
517
+ obs_stop = np.minimum(obs_stop, target_shape)
518
+
519
+ subset = self
520
+ if drop_out_of_box:
521
+ stops = np.subtract(cand_stop, extraction_shape)
522
+ keep_peaks = (
523
+ np.sum(
524
+ np.multiply(cand_start == 0, stops == 0),
525
+ axis=1,
526
+ )
527
+ == self.translations.shape[1]
528
+ )
529
+ n_remaining = keep_peaks.sum()
530
+ if n_remaining == 0:
531
+ print(
532
+ "No peak remaining after filtering. Started with"
533
+ f" {self.translations.shape[0]} filtered to {n_remaining}."
534
+ " Consider reducing min_distance, increase num_peaks or use"
535
+ " a different peak caller."
536
+ )
537
+ exit(-1)
538
+
539
+ cand_start = cand_start[keep_peaks,]
540
+ cand_stop = cand_stop[keep_peaks,]
541
+ obs_start = obs_start[keep_peaks,]
542
+ obs_stop = obs_stop[keep_peaks,]
543
+ subset = self[keep_peaks]
544
+
545
+ cand_start, cand_stop = cand_start.astype(int), cand_stop.astype(int)
546
+ obs_start, obs_stop = obs_start.astype(int), obs_stop.astype(int)
547
+
548
+ candidate_slices = [
549
+ tuple(slice(s, e) for s, e in zip(start_row, stop_row))
550
+ for start_row, stop_row in zip(cand_start, cand_stop)
551
+ ]
552
+
553
+ observation_slices = [
554
+ tuple(slice(s, e) for s, e in zip(start_row, stop_row))
555
+ for start_row, stop_row in zip(obs_start, obs_stop)
556
+ ]
557
+
558
+ if return_orientations:
559
+ return subset, candidate_slices, observation_slices
560
+
561
+ return candidate_slices, observation_slices
tme/preprocessor.py CHANGED
@@ -654,12 +654,9 @@ class Preprocessor:
654
654
  array = template.copy()
655
655
  interpolation_box = array.shape
656
656
 
657
- print(array.shape)
658
-
659
657
  for k in range(template.ndim):
660
658
  array = decimate(array, q=level, axis=k)
661
659
 
662
- print(array.shape)
663
660
  template = zoom(array, np.divide(template.shape, array.shape))
664
661
  template = self.interpolate_box(box=interpolation_box, arr=template)
665
662
 
@@ -768,21 +765,24 @@ class Preprocessor:
768
765
  sigma = sigma_factor * resolution
769
766
  sigma_grid = sigma / sampling_rate
770
767
  sigma_grid2 = sigma_grid * sigma_grid
771
- for index, point in enumerate(np.rollaxis(positions, 0)):
772
- starts = np.maximum(np.ceil(point - cutoff_value * sigma_grid), 0).astype(
773
- int
774
- )
775
- stops = np.minimum(
776
- np.floor(point + cutoff_value * sigma_grid), shape
777
- ).astype(int)
778
768
 
779
- grid_index = np.meshgrid(
780
- *[range(start, stop) for start, stop in zip(starts, stops)]
781
- )
782
- distances = np.einsum(
783
- "aijk->ijk",
784
- np.array([(grid_index[i] - point[i]) ** 2 for i in range(len(point))]),
785
- dtype=np.float64,
769
+ starts = np.maximum(np.ceil(positions - cutoff_value * sigma_grid), 0).astype(
770
+ int
771
+ )
772
+ stops = np.minimum(
773
+ np.floor(positions + cutoff_value * sigma_grid), shape
774
+ ).astype(int)
775
+ ranges = tuple(tuple(zip(start, stop)) for start, stop in zip(starts, stops))
776
+
777
+ positions = positions.reshape(
778
+ *positions.shape, *tuple(1 for _ in range(positions.shape[1]))
779
+ )
780
+ for index in range(positions.shape[0]):
781
+ grid_index = np.meshgrid(*[range(*coord) for coord in ranges[index]])
782
+ distances = np.sum(
783
+ np.square(np.subtract(grid_index, positions[index])),
784
+ dtype=np.float32,
785
+ axis=0,
786
786
  )
787
787
  np.add.at(
788
788
  out,
@@ -1131,6 +1131,7 @@ class Preprocessor:
1131
1131
  stop_tilt: float,
1132
1132
  tilt_step: float,
1133
1133
  shape: Tuple[int],
1134
+ tilt_angles: Tuple[float] = None,
1134
1135
  opening_axis: int = 0,
1135
1136
  tilt_axis: int = 2,
1136
1137
  sigma: float = 0,
@@ -1184,7 +1185,9 @@ class Preprocessor:
1184
1185
  :py:meth:`Preprocessor.wedge_mask`
1185
1186
  :py:meth:`Preprocessor.continuous_wedge_mask`
1186
1187
  """
1187
- tilt_angles = np.arange(-start_tilt, stop_tilt + tilt_step, tilt_step)
1188
+ if tilt_angles is None:
1189
+ tilt_angles = np.arange(-start_tilt, stop_tilt + tilt_step, tilt_step)
1190
+
1188
1191
  plane = np.zeros((shape[opening_axis], shape[tilt_axis]), dtype=np.float32)
1189
1192
  subset = tuple(
1190
1193
  slice(None) if i != 0 else slice(x // 2, x // 2 + 1)
tme/structure.py CHANGED
@@ -26,45 +26,10 @@ from .types import NDArray
26
26
 
27
27
  @dataclass(repr=False)
28
28
  class Structure:
29
- """Represents atomic structures in accordance with the Protein Data Bank (PDB)
29
+ """
30
+ Represents atomic structures in accordance with the Protein Data Bank (PDB)
30
31
  format specification.
31
32
 
32
- Attributes
33
- ----------
34
- record_type : NDArray
35
- Type of the record, e.g., ATOM, HETATM. Array shape = (n,)
36
- atom_serial_number : NDArray
37
- Serial number assigned to each atom. Array shape = (n,)
38
- atom_name : NDArray
39
- Standardized names for each atom. Array shape = (n,)
40
- atom_coordinate : NDArray
41
- The 3D Cartesian coordinates of each atom in x, y, z. Array shape = (n,3 )
42
- alternate_location_indicator : NDArray
43
- Indicator for alternate locations of an atom if it exists in multiple places.
44
- Array shape = (n,)
45
- residue_name : NDArray
46
- Standard residue names where each atom belongs. Array shape = (n,)
47
- chain_identifier : NDArray
48
- Identifier for the chain where each atom is located. Array shape = (n,)
49
- residue_sequence_number : NDArray
50
- Sequence number of the residue in the protein chain for each atom.
51
- Array shape = (n,)
52
- code_for_residue_insertion : NDArray
53
- Code to denote any residue insertion. Array shape = (n,)
54
- occupancy : NDArray
55
- Occupancy factor of each atom, indicating the fraction of time the atom
56
- is located at its position. Array shape = (n,)
57
- temperature_factor : NDArray
58
- Measure of the atomic displacement or B-factor for each atom. Array shape = (n,)
59
- segment_identifier : NDArray
60
- Identifier for the segment where each atom belongs. Array shape = (n,)
61
- element_symbol : NDArray
62
- Atomic element symbol for each atom. Array shape = (n,)
63
- charge : NDArray
64
- Charge on the atom. Array shape = (n,)
65
- details : dict
66
- Any additional or auxiliary details. Array shape = (n,)
67
-
68
33
  References
69
34
  ----------
70
35
  .. [1] https://www.cgl.ucsf.edu/chimera/docs/UsersGuide/tutorials/pdbintro.html