pytme 0.1.5__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 (63) hide show
  1. pytme-0.1.5.data/scripts/estimate_ram_usage.py +81 -0
  2. pytme-0.1.5.data/scripts/match_template.py +744 -0
  3. pytme-0.1.5.data/scripts/postprocess.py +279 -0
  4. pytme-0.1.5.data/scripts/preprocess.py +93 -0
  5. pytme-0.1.5.data/scripts/preprocessor_gui.py +729 -0
  6. pytme-0.1.5.dist-info/LICENSE +153 -0
  7. pytme-0.1.5.dist-info/METADATA +69 -0
  8. pytme-0.1.5.dist-info/RECORD +63 -0
  9. pytme-0.1.5.dist-info/WHEEL +5 -0
  10. pytme-0.1.5.dist-info/entry_points.txt +6 -0
  11. pytme-0.1.5.dist-info/top_level.txt +2 -0
  12. scripts/__init__.py +0 -0
  13. scripts/estimate_ram_usage.py +81 -0
  14. scripts/match_template.py +744 -0
  15. scripts/match_template_devel.py +788 -0
  16. scripts/postprocess.py +279 -0
  17. scripts/preprocess.py +93 -0
  18. scripts/preprocessor_gui.py +729 -0
  19. tme/__init__.py +6 -0
  20. tme/__version__.py +1 -0
  21. tme/analyzer.py +1144 -0
  22. tme/backends/__init__.py +134 -0
  23. tme/backends/cupy_backend.py +309 -0
  24. tme/backends/matching_backend.py +1154 -0
  25. tme/backends/npfftw_backend.py +763 -0
  26. tme/backends/pytorch_backend.py +526 -0
  27. tme/data/__init__.py +0 -0
  28. tme/data/c48n309.npy +0 -0
  29. tme/data/c48n527.npy +0 -0
  30. tme/data/c48n9.npy +0 -0
  31. tme/data/c48u1.npy +0 -0
  32. tme/data/c48u1153.npy +0 -0
  33. tme/data/c48u1201.npy +0 -0
  34. tme/data/c48u1641.npy +0 -0
  35. tme/data/c48u181.npy +0 -0
  36. tme/data/c48u2219.npy +0 -0
  37. tme/data/c48u27.npy +0 -0
  38. tme/data/c48u2947.npy +0 -0
  39. tme/data/c48u3733.npy +0 -0
  40. tme/data/c48u4749.npy +0 -0
  41. tme/data/c48u5879.npy +0 -0
  42. tme/data/c48u7111.npy +0 -0
  43. tme/data/c48u815.npy +0 -0
  44. tme/data/c48u83.npy +0 -0
  45. tme/data/c48u8649.npy +0 -0
  46. tme/data/c600v.npy +0 -0
  47. tme/data/c600vc.npy +0 -0
  48. tme/data/metadata.yaml +80 -0
  49. tme/data/quat_to_numpy.py +42 -0
  50. tme/data/scattering_factors.pickle +0 -0
  51. tme/density.py +2314 -0
  52. tme/extensions.cpython-311-darwin.so +0 -0
  53. tme/helpers.py +881 -0
  54. tme/matching_data.py +377 -0
  55. tme/matching_exhaustive.py +1553 -0
  56. tme/matching_memory.py +382 -0
  57. tme/matching_optimization.py +1123 -0
  58. tme/matching_utils.py +1180 -0
  59. tme/parser.py +429 -0
  60. tme/preprocessor.py +1291 -0
  61. tme/scoring.py +866 -0
  62. tme/structure.py +1428 -0
  63. tme/types.py +10 -0
tme/matching_data.py ADDED
@@ -0,0 +1,377 @@
1
+ """ Data class for holding template matching data.
2
+
3
+ Copyright (c) 2023 European Molecular Biology Laboratory
4
+
5
+ Author: Valentin Maurer <valentin.maurer@embl-hamburg.de>
6
+ """
7
+
8
+ from typing import Tuple, List
9
+
10
+ import numpy as np
11
+ from numpy.typing import NDArray
12
+
13
+ from . import Density
14
+ from .backends import backend
15
+ from .matching_utils import compute_full_convolution_index
16
+
17
+
18
+ class MatchingData:
19
+ """
20
+ Contains data required for template matching.
21
+
22
+ Parameters
23
+ ----------
24
+ target : np.ndarray or Density
25
+ Target data array for template matching.
26
+ template : np.ndarray or Density
27
+ Template data array for template matching.
28
+
29
+ """
30
+
31
+ def __init__(self, target: NDArray, template: NDArray):
32
+ self._default_dtype = np.float32
33
+ self._complex_dtype = np.complex64
34
+
35
+ self._target = target
36
+ self._target_mask = None
37
+ self._template_mask = None
38
+ self._translation_offset = np.zeros(len(target.shape), dtype=int)
39
+
40
+ self.template = template
41
+
42
+ self._target_pad = np.zeros(len(target.shape), dtype=int)
43
+ self._template_pad = np.zeros(len(template.shape), dtype=int)
44
+
45
+ self.template_filter = {}
46
+ self.target_filter = {}
47
+
48
+ self._invert_target = False
49
+
50
+ @staticmethod
51
+ def _shape_to_slice(shape: Tuple[int]):
52
+ return tuple(slice(0, dim) for dim in shape)
53
+
54
+ @classmethod
55
+ def _slice_to_mesh(cls, slice_variable: (slice,), shape: (int,)):
56
+ if slice_variable is None:
57
+ slice_variable = cls._shape_to_slice(shape)
58
+ ranges = [range(slc.start, slc.stop) for slc in slice_variable]
59
+ indices = np.meshgrid(*ranges, sparse=True, indexing="ij")
60
+ return indices
61
+
62
+ @staticmethod
63
+ def _load_array(arr: NDArray):
64
+ """
65
+ Load ``arr``, If ``arr`` type is memmap, reload from disk.
66
+
67
+ Parameters
68
+ ----------
69
+ arr : NDArray
70
+ Array to load.
71
+
72
+ Returns
73
+ -------
74
+ NDArray
75
+ Loaded array.
76
+ """
77
+
78
+ if type(arr) == np.memmap:
79
+ return np.memmap(arr.filename, mode="r", shape=arr.shape, dtype=arr.dtype)
80
+ return arr
81
+
82
+ def subset_array(
83
+ self, arr: NDArray, arr_slice: Tuple[slice], padding: NDArray
84
+ ) -> NDArray:
85
+ """
86
+ Extract a subset of the input array according to the given slice and
87
+ apply padding.
88
+
89
+ Parameters
90
+ ----------
91
+ arr : NDArray
92
+ The input array from which a subset is extracted.
93
+ arr_slice : tuple of slice
94
+ Defines the region of the input array to be extracted.
95
+ padding : NDArray
96
+ Padding values for each dimension. If the padding exceeds the array
97
+ dimensions, the extra regions are filled with the mean of the array
98
+ values, otherwise, the
99
+ values in ``arr`` are used.
100
+
101
+ Returns
102
+ -------
103
+ NDArray
104
+ Subset of the input array with padding applied.
105
+ """
106
+ padding = np.maximum(padding, 0)
107
+
108
+ slice_start = np.array([x.start for x in arr_slice], dtype=int)
109
+ slice_stop = np.array([x.stop for x in arr_slice], dtype=int)
110
+ slice_shape = np.subtract(slice_stop, slice_start)
111
+
112
+ padding = np.add(padding, np.mod(padding, 2))
113
+ left_pad = right_pad = np.divide(padding, 2).astype(int)
114
+
115
+ data_voxels_left = np.minimum(slice_start, left_pad)
116
+ data_voxels_right = np.minimum(
117
+ np.subtract(arr.shape, slice_stop), right_pad
118
+ ).astype(int)
119
+
120
+ ret_shape = np.add(slice_shape, padding)
121
+ arr_start = np.subtract(slice_start, data_voxels_left)
122
+ arr_stop = np.add(slice_stop, data_voxels_right)
123
+ arr_slice = tuple(slice(*pos) for pos in zip(arr_start, arr_stop))
124
+ arr_mesh = self._slice_to_mesh(arr_slice, arr.shape)
125
+
126
+ subset_start = np.subtract(left_pad, data_voxels_left)
127
+ subset_stop = np.add(subset_start, np.subtract(arr_stop, arr_start))
128
+ subset_slice = tuple(slice(*prod) for prod in zip(subset_start, subset_stop))
129
+ subset_mesh = self._slice_to_mesh(subset_slice, ret_shape)
130
+
131
+ if type(arr) == Density:
132
+ if type(arr.data) == np.memmap:
133
+ arr = Density.from_file(arr.data.filename, subset=arr_slice).data
134
+ else:
135
+ arr = np.asarray(arr.data[*arr_mesh])
136
+ else:
137
+ if type(arr) == np.memmap:
138
+ arr = np.memmap(
139
+ arr.filename, mode="r", shape=arr.shape, dtype=arr.dtype
140
+ )
141
+ arr = np.asarray(arr[*arr_mesh])
142
+ ret = np.full(
143
+ shape=np.add(slice_shape, padding), fill_value=arr.mean(), dtype=arr.dtype
144
+ )
145
+ ret[*subset_mesh] = arr
146
+
147
+ return ret
148
+
149
+ def subset_by_slice(
150
+ self,
151
+ target_slice: Tuple[slice] = None,
152
+ template_slice: Tuple[slice] = None,
153
+ target_pad: NDArray = None,
154
+ template_pad: NDArray = None,
155
+ invert_target: bool = False,
156
+ ) -> "MatchingData":
157
+ """
158
+ Slice the instance arrays based on the provided slices.
159
+
160
+ Parameters
161
+ ----------
162
+ target_slice : tuple of slice, optional
163
+ Slices for the target. If not provided, the full shape is used.
164
+ template_slice : tuple of slice, optional
165
+ Slices for the template. If not provided, the full shape is used.
166
+ target_pad : NDArray, optional
167
+ Padding for target. Defaults to zeros. If padding exceeds target,
168
+ pad with mean.
169
+ template_pad : NDArray, optional
170
+ Padding for template. Defaults to zeros. If padding exceeds template,
171
+ pad with mean.
172
+
173
+ Returns
174
+ -------
175
+ MatchingData
176
+ Newly allocated sliced class instance.
177
+ """
178
+ target_shape = self.target.shape
179
+ template_shape = self._template.shape
180
+
181
+ if target_slice is None:
182
+ target_slice = self._shape_to_slice(target_shape)
183
+ if template_slice is None:
184
+ template_slice = self._shape_to_slice(template_shape)
185
+
186
+ if target_pad is None:
187
+ target_pad = np.zeros(len(self.target.shape), dtype=int)
188
+ if template_pad is None:
189
+ template_pad = np.zeros(len(self.target.shape), dtype=int)
190
+
191
+ indices = compute_full_convolution_index(
192
+ outer_shape=self._target.shape,
193
+ inner_shape=self._template.shape,
194
+ outer_split=target_slice,
195
+ inner_split=template_slice,
196
+ )
197
+
198
+ target_subset = self.subset_array(
199
+ arr=self._target, arr_slice=target_slice, padding=target_pad
200
+ )
201
+ if self._invert_target:
202
+ target_subset *= -1
203
+ target_min, target_max = target_subset.min(), target_subset.max()
204
+ target_subset = (target_subset - target_min) / (target_max - target_min)
205
+ template_subset = self.subset_array(
206
+ arr=self._template,
207
+ arr_slice=template_slice,
208
+ padding=template_pad,
209
+ )
210
+ ret = self.__class__(target=target_subset, template=template_subset)
211
+
212
+ ret._translation_offset = np.add(
213
+ [x.start for x in target_slice],
214
+ [x.start for x in template_slice],
215
+ )
216
+ ret.template_filter = self.template_filter
217
+
218
+ ret.rotations, ret.indices = self.rotations, indices
219
+ ret._target_pad, ret._template_pad = target_pad, template_pad
220
+ ret._invert_target = self._invert_target
221
+
222
+ if self._target_mask is not None:
223
+ ret.target_mask = self.subset_array(
224
+ arr=self._target_mask, arr_slice=target_slice, padding=target_pad
225
+ )
226
+ if self._template_mask is not None:
227
+ ret.template_mask = self.subset_array(
228
+ arr=self._template_mask,
229
+ arr_slice=template_slice,
230
+ padding=template_pad,
231
+ )
232
+
233
+ return ret
234
+
235
+ def to_backend(self) -> None:
236
+ """
237
+ Transfer the class instance's numpy arrays to the current backend.
238
+ """
239
+ for attr_name, attr_value in vars(self).items():
240
+ if isinstance(attr_value, np.ndarray):
241
+ converted_array = backend.to_backend_array(attr_value.copy())
242
+ setattr(self, attr_name, converted_array)
243
+
244
+ self._default_dtype = backend._default_dtype
245
+ self._complex_dtype = backend._complex_dtype
246
+
247
+ @property
248
+ def rotations(self):
249
+ """Return rotation matrices used for fitting."""
250
+ return self._rotations
251
+
252
+ @rotations.setter
253
+ def rotations(self, rotations: NDArray):
254
+ """
255
+ Set and reshape the rotation matrices for fitting.
256
+
257
+ Parameters
258
+ ----------
259
+ rotations : NDArray
260
+ Rotations in shape (3 x 3), (1 x 3 x 3), or (n x k x k).
261
+ """
262
+ if rotations.__class__ != np.ndarray:
263
+ raise ValueError("Rotation set has to be of type numpy ndarray.")
264
+ if rotations.ndim == 2:
265
+ print("Reshaping rotations array to rank 3.")
266
+ rotations = rotations.reshape(1, *rotations.shape)
267
+ elif rotations.ndim == 3:
268
+ pass
269
+ else:
270
+ raise ValueError("Rotations have to be a rank 2 or 3 array.")
271
+ self._rotations = rotations.astype(self._default_dtype)
272
+
273
+ @property
274
+ def target(self):
275
+ """Returns the target NDArray."""
276
+ if type(self._target) == Density:
277
+ return self._target.data
278
+ return self._target
279
+
280
+ @property
281
+ def template(self):
282
+ """Returns the reversed template NDArray."""
283
+ if type(self._template) == Density:
284
+ return backend.reverse(self._template.data)
285
+ return backend.reverse(self._template)
286
+
287
+ @template.setter
288
+ def template(self, template: NDArray):
289
+ """
290
+ Set the template array.
291
+
292
+ Parameters
293
+ ----------
294
+ template : NDArray
295
+ Array to set as the template.
296
+ """
297
+ if type(template) == Density:
298
+ template.data = template.data.astype(self._default_dtype, copy=False)
299
+ self._template = template
300
+ self._templateshape = self._template.shape[::-1]
301
+ return None
302
+ self._template = template.astype(self._default_dtype, copy=False)
303
+ self._templateshape = self._template.shape[::-1]
304
+
305
+ @property
306
+ def target_mask(self):
307
+ """Returns the target mask NDArray."""
308
+ if type(self._target_mask) == Density:
309
+ return self._target_mask.data
310
+ return self._target_mask
311
+
312
+ @target_mask.setter
313
+ def target_mask(self, mask: NDArray):
314
+ """Sets the target mask."""
315
+ if not np.all(self.target.shape == mask.shape):
316
+ raise ValueError("Target and its mask have to have the same shape.")
317
+
318
+ if type(mask) == Density:
319
+ mask.data = mask.data.astype(self._default_dtype, copy=False)
320
+ self._target_mask = mask
321
+ self._targetmaskshape = self._target_mask.shape[::-1]
322
+ return None
323
+ self._target_mask = mask.astype(self._default_dtype, copy=False)
324
+ self._targetmaskshape = self._target_mask.shape
325
+
326
+ @property
327
+ def template_mask(self):
328
+ """
329
+ Set the template mask array after reversing it.
330
+
331
+ Parameters
332
+ ----------
333
+ template : NDArray
334
+ Array to set as the template.
335
+ """
336
+ if type(self._template_mask) == Density:
337
+ return backend.reverse(self._template_mask.data)
338
+ return backend.reverse(self._template_mask)
339
+
340
+ @template_mask.setter
341
+ def template_mask(self, mask: NDArray):
342
+ """Returns the reversed template mask NDArray."""
343
+ if not np.all(self._template.shape == mask.shape):
344
+ raise ValueError("Target and its mask have to have the same shape.")
345
+
346
+ if type(mask) == Density:
347
+ mask.data = mask.data.astype(self._default_dtype, copy=False)
348
+ self._template_mask = mask
349
+ self._templatemaskshape = self._template_mask.shape[::-1]
350
+ return None
351
+
352
+ self._template_mask = mask.astype(self._default_dtype, copy=False)
353
+ self._templatemaskshape = self._template_mask.shape[::-1]
354
+
355
+ def _split_rotations_on_jobs(self, n_jobs: int) -> List[NDArray]:
356
+ """
357
+ Split the rotation matrices into parts based on the number of jobs.
358
+
359
+ Parameters
360
+ ----------
361
+ n_jobs : int
362
+ Number of jobs for splitting.
363
+
364
+ Returns
365
+ -------
366
+ list of NDArray
367
+ List of split rotation matrices.
368
+ """
369
+ nrot_per_job = self.rotations.shape[0] // n_jobs
370
+ rot_list = []
371
+ for n in range(n_jobs):
372
+ init_rot = n * nrot_per_job
373
+ end_rot = init_rot + nrot_per_job
374
+ if n == n_jobs - 1:
375
+ end_rot = None
376
+ rot_list.append(self.rotations[init_rot:end_rot])
377
+ return rot_list