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.
- pytme-0.1.5.data/scripts/estimate_ram_usage.py +81 -0
- pytme-0.1.5.data/scripts/match_template.py +744 -0
- pytme-0.1.5.data/scripts/postprocess.py +279 -0
- pytme-0.1.5.data/scripts/preprocess.py +93 -0
- pytme-0.1.5.data/scripts/preprocessor_gui.py +729 -0
- pytme-0.1.5.dist-info/LICENSE +153 -0
- pytme-0.1.5.dist-info/METADATA +69 -0
- pytme-0.1.5.dist-info/RECORD +63 -0
- pytme-0.1.5.dist-info/WHEEL +5 -0
- pytme-0.1.5.dist-info/entry_points.txt +6 -0
- pytme-0.1.5.dist-info/top_level.txt +2 -0
- scripts/__init__.py +0 -0
- scripts/estimate_ram_usage.py +81 -0
- scripts/match_template.py +744 -0
- scripts/match_template_devel.py +788 -0
- scripts/postprocess.py +279 -0
- scripts/preprocess.py +93 -0
- scripts/preprocessor_gui.py +729 -0
- tme/__init__.py +6 -0
- tme/__version__.py +1 -0
- tme/analyzer.py +1144 -0
- tme/backends/__init__.py +134 -0
- tme/backends/cupy_backend.py +309 -0
- tme/backends/matching_backend.py +1154 -0
- tme/backends/npfftw_backend.py +763 -0
- tme/backends/pytorch_backend.py +526 -0
- tme/data/__init__.py +0 -0
- tme/data/c48n309.npy +0 -0
- tme/data/c48n527.npy +0 -0
- tme/data/c48n9.npy +0 -0
- tme/data/c48u1.npy +0 -0
- tme/data/c48u1153.npy +0 -0
- tme/data/c48u1201.npy +0 -0
- tme/data/c48u1641.npy +0 -0
- tme/data/c48u181.npy +0 -0
- tme/data/c48u2219.npy +0 -0
- tme/data/c48u27.npy +0 -0
- tme/data/c48u2947.npy +0 -0
- tme/data/c48u3733.npy +0 -0
- tme/data/c48u4749.npy +0 -0
- tme/data/c48u5879.npy +0 -0
- tme/data/c48u7111.npy +0 -0
- tme/data/c48u815.npy +0 -0
- tme/data/c48u83.npy +0 -0
- tme/data/c48u8649.npy +0 -0
- tme/data/c600v.npy +0 -0
- tme/data/c600vc.npy +0 -0
- tme/data/metadata.yaml +80 -0
- tme/data/quat_to_numpy.py +42 -0
- tme/data/scattering_factors.pickle +0 -0
- tme/density.py +2314 -0
- tme/extensions.cpython-311-darwin.so +0 -0
- tme/helpers.py +881 -0
- tme/matching_data.py +377 -0
- tme/matching_exhaustive.py +1553 -0
- tme/matching_memory.py +382 -0
- tme/matching_optimization.py +1123 -0
- tme/matching_utils.py +1180 -0
- tme/parser.py +429 -0
- tme/preprocessor.py +1291 -0
- tme/scoring.py +866 -0
- tme/structure.py +1428 -0
- 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
|