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.
- {pytme-0.2.1.data → pytme-0.2.3.data}/scripts/match_template.py +219 -216
- {pytme-0.2.1.data → pytme-0.2.3.data}/scripts/postprocess.py +86 -54
- pytme-0.2.3.data/scripts/preprocess.py +132 -0
- {pytme-0.2.1.data → pytme-0.2.3.data}/scripts/preprocessor_gui.py +181 -94
- pytme-0.2.3.dist-info/METADATA +92 -0
- pytme-0.2.3.dist-info/RECORD +75 -0
- {pytme-0.2.1.dist-info → pytme-0.2.3.dist-info}/WHEEL +1 -1
- pytme-0.2.1.data/scripts/preprocess.py → scripts/eval.py +1 -1
- scripts/extract_candidates.py +20 -13
- scripts/match_template.py +219 -216
- scripts/match_template_filters.py +154 -95
- scripts/postprocess.py +86 -54
- scripts/preprocess.py +95 -56
- scripts/preprocessor_gui.py +181 -94
- scripts/refine_matches.py +265 -61
- tme/__init__.py +0 -1
- tme/__version__.py +1 -1
- tme/analyzer.py +458 -813
- tme/backends/__init__.py +40 -11
- tme/backends/_jax_utils.py +187 -0
- tme/backends/cupy_backend.py +109 -226
- tme/backends/jax_backend.py +230 -152
- tme/backends/matching_backend.py +445 -384
- tme/backends/mlx_backend.py +32 -59
- tme/backends/npfftw_backend.py +240 -507
- tme/backends/pytorch_backend.py +30 -151
- tme/density.py +248 -371
- tme/extensions.cpython-311-darwin.so +0 -0
- tme/matching_data.py +328 -284
- tme/matching_exhaustive.py +195 -1499
- tme/matching_optimization.py +143 -106
- tme/matching_scores.py +887 -0
- tme/matching_utils.py +287 -388
- tme/memory.py +377 -0
- tme/orientations.py +78 -21
- tme/parser.py +3 -4
- tme/preprocessing/_utils.py +61 -32
- tme/preprocessing/composable_filter.py +7 -4
- tme/preprocessing/compose.py +7 -3
- tme/preprocessing/frequency_filters.py +49 -39
- tme/preprocessing/tilt_series.py +44 -72
- tme/preprocessor.py +560 -526
- tme/structure.py +491 -188
- tme/types.py +5 -3
- pytme-0.2.1.dist-info/METADATA +0 -73
- pytme-0.2.1.dist-info/RECORD +0 -73
- tme/helpers.py +0 -881
- tme/matching_constrained.py +0 -195
- {pytme-0.2.1.data → pytme-0.2.3.data}/scripts/estimate_ram_usage.py +0 -0
- {pytme-0.2.1.dist-info → pytme-0.2.3.dist-info}/LICENSE +0 -0
- {pytme-0.2.1.dist-info → pytme-0.2.3.dist-info}/entry_points.txt +0 -0
- {pytme-0.2.1.dist-info → pytme-0.2.3.dist-info}/top_level.txt +0 -0
tme/matching_optimization.py
CHANGED
@@ -10,21 +10,23 @@ from typing import Tuple, Dict
|
|
10
10
|
from abc import ABC, abstractmethod
|
11
11
|
|
12
12
|
import numpy as np
|
13
|
-
from
|
13
|
+
from scipy.spatial import KDTree
|
14
|
+
from scipy.ndimage import laplace, map_coordinates
|
14
15
|
from scipy.optimize import (
|
15
|
-
differential_evolution,
|
16
|
-
LinearConstraint,
|
17
|
-
basinhopping,
|
18
16
|
minimize,
|
17
|
+
basinhopping,
|
18
|
+
LinearConstraint,
|
19
|
+
differential_evolution,
|
19
20
|
)
|
20
|
-
from scipy.ndimage import laplace, map_coordinates
|
21
|
-
from scipy.spatial import KDTree
|
22
21
|
|
23
|
-
from .
|
24
|
-
from .
|
22
|
+
from .backends import backend as be
|
23
|
+
from .types import ArrayLike, NDArray
|
25
24
|
from .matching_data import MatchingData
|
26
|
-
from .matching_utils import
|
27
|
-
|
25
|
+
from .matching_utils import (
|
26
|
+
rigid_transform,
|
27
|
+
euler_to_rotationmatrix,
|
28
|
+
normalize_template,
|
29
|
+
)
|
28
30
|
|
29
31
|
|
30
32
|
def _format_rigid_transform(x: Tuple[float]) -> Tuple[ArrayLike, ArrayLike]:
|
@@ -45,9 +47,9 @@ def _format_rigid_transform(x: Tuple[float]) -> Tuple[ArrayLike, ArrayLike]:
|
|
45
47
|
split = len(x) // 2
|
46
48
|
translation, angles = x[:split], x[split:]
|
47
49
|
|
48
|
-
translation =
|
49
|
-
rotation_matrix = euler_to_rotationmatrix(
|
50
|
-
rotation_matrix =
|
50
|
+
translation = be.to_backend_array(translation)
|
51
|
+
rotation_matrix = euler_to_rotationmatrix(be.to_numpy_array(angles))
|
52
|
+
rotation_matrix = be.to_backend_array(rotation_matrix)
|
51
53
|
|
52
54
|
return translation, rotation_matrix
|
53
55
|
|
@@ -91,6 +93,7 @@ class _MatchDensityToDensity(ABC):
|
|
91
93
|
negate_score: bool = True,
|
92
94
|
**kwargs: Dict,
|
93
95
|
):
|
96
|
+
self.eps = be.eps(target.dtype)
|
94
97
|
self.rotate_mask = rotate_mask
|
95
98
|
self.interpolation_order = interpolation_order
|
96
99
|
|
@@ -103,15 +106,13 @@ class _MatchDensityToDensity(ABC):
|
|
103
106
|
self.target, self.target_mask = matching_data.target, matching_data.target_mask
|
104
107
|
|
105
108
|
self.template = matching_data._template
|
106
|
-
self.template_rot =
|
107
|
-
template.shape, backend._float_dtype
|
108
|
-
)
|
109
|
+
self.template_rot = be.zeros(template.shape, be._float_dtype)
|
109
110
|
|
110
111
|
self.template_mask, self.template_mask_rot = 1, 1
|
111
112
|
rotate_mask = False if matching_data._template_mask is None else rotate_mask
|
112
113
|
if matching_data.template_mask is not None:
|
113
114
|
self.template_mask = matching_data._template_mask
|
114
|
-
self.template_mask_rot =
|
115
|
+
self.template_mask_rot = be.topleft_pad(
|
115
116
|
matching_data._template_mask, self.template_mask.shape
|
116
117
|
)
|
117
118
|
|
@@ -226,29 +227,25 @@ class _MatchDensityToDensity(ABC):
|
|
226
227
|
translation, rotation_matrix = _format_rigid_transform(x)
|
227
228
|
self.template_rot.fill(0)
|
228
229
|
|
229
|
-
voxel_translation =
|
230
|
-
subvoxel_translation =
|
230
|
+
voxel_translation = be.astype(translation, be._int_dtype)
|
231
|
+
subvoxel_translation = be.subtract(translation, voxel_translation)
|
231
232
|
|
232
|
-
center =
|
233
|
-
|
234
|
-
)
|
235
|
-
right_pad = backend.subtract(self.template.shape, center)
|
233
|
+
center = be.astype(be.divide(self.template.shape, 2), be._int_dtype)
|
234
|
+
right_pad = be.subtract(self.template.shape, center)
|
236
235
|
|
237
|
-
translated_center =
|
236
|
+
translated_center = be.add(voxel_translation, center)
|
238
237
|
|
239
|
-
target_starts =
|
240
|
-
target_stops =
|
238
|
+
target_starts = be.subtract(translated_center, center)
|
239
|
+
target_stops = be.add(translated_center, right_pad)
|
241
240
|
|
242
|
-
template_starts =
|
243
|
-
|
241
|
+
template_starts = be.subtract(be.maximum(target_starts, 0), target_starts)
|
242
|
+
template_stops = be.subtract(
|
243
|
+
target_stops, be.minimum(target_stops, self.target.shape)
|
244
244
|
)
|
245
|
-
template_stops =
|
246
|
-
target_stops, backend.minimum(target_stops, self.target.shape)
|
247
|
-
)
|
248
|
-
template_stops = backend.subtract(self.template.shape, template_stops)
|
245
|
+
template_stops = be.subtract(self.template.shape, template_stops)
|
249
246
|
|
250
|
-
target_starts =
|
251
|
-
target_stops =
|
247
|
+
target_starts = be.maximum(target_starts, 0)
|
248
|
+
target_stops = be.minimum(target_stops, self.target.shape)
|
252
249
|
|
253
250
|
cand_start, cand_stop = template_starts.astype(int), template_stops.astype(int)
|
254
251
|
obs_start, obs_stop = target_starts.astype(int), target_stops.astype(int)
|
@@ -285,11 +282,11 @@ class _MatchCoordinatesToDensity(_MatchDensityToDensity):
|
|
285
282
|
target : NDArray
|
286
283
|
A d-dimensional target to match the template coordinate set to.
|
287
284
|
template_coordinates : NDArray
|
288
|
-
Template coordinate array with shape
|
285
|
+
Template coordinate array with shape (d,n).
|
289
286
|
template_weights : NDArray
|
290
|
-
Template weight array with shape
|
287
|
+
Template weight array with shape (n,).
|
291
288
|
template_mask_coordinates : NDArray, optional
|
292
|
-
Template mask coordinates with shape
|
289
|
+
Template mask coordinates with shape (d,n).
|
293
290
|
target_mask : NDArray, optional
|
294
291
|
A d-dimensional mask to be applied to the target.
|
295
292
|
negate_score : bool, optional
|
@@ -308,6 +305,7 @@ class _MatchCoordinatesToDensity(_MatchDensityToDensity):
|
|
308
305
|
negate_score: bool = True,
|
309
306
|
**kwargs: Dict,
|
310
307
|
):
|
308
|
+
self.eps = be.eps(target.dtype)
|
311
309
|
self.target_density = target
|
312
310
|
self.target_mask_density = target_mask
|
313
311
|
|
@@ -316,6 +314,8 @@ class _MatchCoordinatesToDensity(_MatchDensityToDensity):
|
|
316
314
|
self.template_coordinates_rotated = np.copy(self.template_coordinates).astype(
|
317
315
|
np.float32
|
318
316
|
)
|
317
|
+
if template_mask_coordinates is None:
|
318
|
+
template_mask_coordinates = template_coordinates.copy()
|
319
319
|
|
320
320
|
self.template_mask_coordinates = template_mask_coordinates
|
321
321
|
self.template_mask_coordinates_rotated = template_mask_coordinates
|
@@ -330,9 +330,9 @@ class _MatchCoordinatesToDensity(_MatchDensityToDensity):
|
|
330
330
|
self.in_volume, self.in_volume_mask = self.map_coordinates_to_array(
|
331
331
|
coordinates=self.template_coordinates_rotated,
|
332
332
|
coordinates_mask=self.template_mask_coordinates_rotated,
|
333
|
-
array_origin=
|
333
|
+
array_origin=be.zeros(target.ndim),
|
334
334
|
array_shape=self.target_density.shape,
|
335
|
-
sampling_rate=
|
335
|
+
sampling_rate=be.full(target.ndim, fill_value=1),
|
336
336
|
)
|
337
337
|
|
338
338
|
if hasattr(self, "_post_init"):
|
@@ -368,9 +368,9 @@ class _MatchCoordinatesToDensity(_MatchDensityToDensity):
|
|
368
368
|
self.in_volume, self.in_volume_mask = self.map_coordinates_to_array(
|
369
369
|
coordinates=self.template_coordinates_rotated,
|
370
370
|
coordinates_mask=self.template_mask_coordinates_rotated,
|
371
|
-
array_origin=
|
371
|
+
array_origin=be.zeros(rotation_matrix.shape[0]),
|
372
372
|
array_shape=self.target_density.shape,
|
373
|
-
sampling_rate=
|
373
|
+
sampling_rate=be.full(rotation_matrix.shape[0], fill_value=1),
|
374
374
|
)
|
375
375
|
|
376
376
|
return self()
|
@@ -584,57 +584,41 @@ class FLC(_MatchDensityToDensity):
|
|
584
584
|
|
585
585
|
def _post_init(self, **kwargs: Dict):
|
586
586
|
if self.target_mask is not None:
|
587
|
-
|
587
|
+
be.multiply(self.target, self.target_mask, out=self.target)
|
588
588
|
|
589
|
-
self.target_square =
|
589
|
+
self.target_square = be.square(self.target)
|
590
590
|
|
591
|
-
|
591
|
+
normalize_template(
|
592
592
|
template=self.template,
|
593
593
|
mask=self.template_mask,
|
594
|
-
|
594
|
+
n_observations=be.sum(self.template_mask),
|
595
595
|
)
|
596
596
|
|
597
597
|
def __call__(self) -> float:
|
598
598
|
"""Returns the score of the current configuration."""
|
599
|
-
|
599
|
+
n_obs = be.sum(self.template_mask_rot)
|
600
600
|
|
601
|
-
|
601
|
+
normalize_template(
|
602
602
|
template=self.template_rot,
|
603
603
|
mask=self.template_mask_rot,
|
604
|
-
|
604
|
+
n_observations=n_obs,
|
605
605
|
)
|
606
|
-
overlap =
|
607
|
-
|
606
|
+
overlap = be.sum(
|
607
|
+
be.multiply(
|
608
608
|
self.template_rot[self.template_slices], self.target[self.target_slices]
|
609
609
|
)
|
610
610
|
)
|
611
611
|
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
self.target_square[self.target_slices],
|
616
|
-
self.template_mask_rot[self.template_slices],
|
617
|
-
)
|
618
|
-
),
|
619
|
-
n_observations,
|
620
|
-
)
|
621
|
-
e2x = backend.square(
|
622
|
-
backend.divide(
|
623
|
-
backend.sum(
|
624
|
-
backend.multiply(
|
625
|
-
self.target[self.target_slices],
|
626
|
-
self.template_mask_rot[self.template_slices],
|
627
|
-
)
|
628
|
-
),
|
629
|
-
n_observations,
|
630
|
-
)
|
631
|
-
)
|
612
|
+
mask_rot = self.template_mask_rot[self.template_slices]
|
613
|
+
exp_sq = be.sum(self.target_square[self.target_slices] * mask_rot) / n_obs
|
614
|
+
sq_exp = be.square(be.sum(self.target[self.target_slices] * mask_rot) / n_obs)
|
632
615
|
|
633
|
-
denominator =
|
634
|
-
denominator =
|
635
|
-
denominator
|
616
|
+
denominator = be.maximum(be.subtract(exp_sq, sq_exp), 0.0)
|
617
|
+
denominator = be.sqrt(denominator)
|
618
|
+
if denominator < self.eps:
|
619
|
+
return 0
|
636
620
|
|
637
|
-
score =
|
621
|
+
score = be.divide(overlap, denominator * n_obs) * self.score_sign
|
638
622
|
return score
|
639
623
|
|
640
624
|
|
@@ -712,19 +696,22 @@ class NormalizedCrossCorrelation(CrossCorrelation):
|
|
712
696
|
__doc__ += _MatchCoordinatesToDensity.__doc__
|
713
697
|
|
714
698
|
def __call__(self) -> float:
|
715
|
-
n_observations =
|
716
|
-
target_coordinates =
|
699
|
+
n_observations = be.sum(self.in_volume_mask)
|
700
|
+
target_coordinates = be.astype(
|
717
701
|
self.template_mask_coordinates_rotated[:, self.in_volume_mask], int
|
718
702
|
)
|
719
703
|
target_weight = self.target_density[tuple(target_coordinates)]
|
720
|
-
ex2 =
|
721
|
-
e2x =
|
704
|
+
ex2 = be.divide(be.sum(be.square(target_weight)), n_observations)
|
705
|
+
e2x = be.square(be.divide(be.sum(target_weight), n_observations))
|
722
706
|
|
723
|
-
denominator =
|
724
|
-
denominator =
|
725
|
-
denominator =
|
726
|
-
self.denominator = denominator
|
707
|
+
denominator = be.maximum(be.subtract(ex2, e2x), 0.0)
|
708
|
+
denominator = be.sqrt(denominator)
|
709
|
+
denominator = be.multiply(denominator, n_observations)
|
727
710
|
|
711
|
+
if denominator <= self.eps:
|
712
|
+
return 0.0
|
713
|
+
|
714
|
+
self.denominator = denominator
|
728
715
|
return super().__call__()
|
729
716
|
|
730
717
|
|
@@ -1096,7 +1083,7 @@ def register_matching_optimization(match_name: str, match_class: type):
|
|
1096
1083
|
|
1097
1084
|
def create_score_object(score: str, **kwargs) -> object:
|
1098
1085
|
"""
|
1099
|
-
Initialize score object with name ``score`` using
|
1086
|
+
Initialize score object with name ``score`` using ``**kwargs``.
|
1100
1087
|
|
1101
1088
|
Parameters
|
1102
1089
|
----------
|
@@ -1118,6 +1105,32 @@ def create_score_object(score: str, **kwargs) -> object:
|
|
1118
1105
|
See Also
|
1119
1106
|
--------
|
1120
1107
|
:py:meth:`register_matching_optimization`
|
1108
|
+
|
1109
|
+
Examples
|
1110
|
+
--------
|
1111
|
+
>>> from tme import Density
|
1112
|
+
>>> from tme.matching_utils import create_mask, euler_to_rotationmatrix
|
1113
|
+
>>> from tme.matching_optimization import CrossCorrelation, optimize_match
|
1114
|
+
>>> translation, rotation = (5, -2, 7), (5, -10, 2)
|
1115
|
+
>>> target = create_mask(
|
1116
|
+
>>> mask_type="ellipse",
|
1117
|
+
>>> radius=(5,5,5),
|
1118
|
+
>>> shape=(51,51,51),
|
1119
|
+
>>> center=(25,25,25),
|
1120
|
+
>>> ).astype(float)
|
1121
|
+
>>> template = Density(data=target)
|
1122
|
+
>>> template = template.rigid_transform(
|
1123
|
+
>>> translation=translation,
|
1124
|
+
>>> rotation_matrix=euler_to_rotationmatrix(rotation),
|
1125
|
+
>>> )
|
1126
|
+
>>> template_coordinates = template.to_pointcloud(0)
|
1127
|
+
>>> template_weights = template.data[tuple(template_coordinates)]
|
1128
|
+
>>> score_object = CrossCorrelation(
|
1129
|
+
>>> target=target,
|
1130
|
+
>>> template_coordinates=template_coordinates,
|
1131
|
+
>>> template_weights=template_weights,
|
1132
|
+
>>> negate_score=True # Multiply returned score with -1 for minimization
|
1133
|
+
>>> )
|
1121
1134
|
"""
|
1122
1135
|
|
1123
1136
|
score_object = MATCHING_OPTIMIZATION_REGISTER.get(score, None)
|
@@ -1137,11 +1150,11 @@ def optimize_match(
|
|
1137
1150
|
bounds_translation: Tuple[Tuple[float]] = None,
|
1138
1151
|
bounds_rotation: Tuple[Tuple[float]] = None,
|
1139
1152
|
optimization_method: str = "basinhopping",
|
1140
|
-
maxiter: int =
|
1153
|
+
maxiter: int = 50,
|
1141
1154
|
x0: Tuple[float] = None,
|
1142
1155
|
) -> Tuple[ArrayLike, ArrayLike, float]:
|
1143
1156
|
"""
|
1144
|
-
Find the translation and rotation optimizing the score returned by
|
1157
|
+
Find the translation and rotation optimizing the score returned by ``score_object``
|
1145
1158
|
with respect to provided bounds.
|
1146
1159
|
|
1147
1160
|
Parameters
|
@@ -1158,40 +1171,68 @@ def optimize_match(
|
|
1158
1171
|
Bounds on the evaluated zyx Euler angles. Has to be specified per dimension
|
1159
1172
|
as tuple of (min, max). Default is None.
|
1160
1173
|
optimization_method : str, optional
|
1161
|
-
Optimizer that will be used, by default
|
1174
|
+
Optimizer that will be used, basinhopping by default. For further
|
1162
1175
|
information refer to :doc:`scipy:reference/optimize`.
|
1163
1176
|
|
1164
|
-
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
1168
|
-
|
|
1169
|
-
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
1177
|
+
+------------------------+-------------------------------------------+
|
1178
|
+
| differential_evolution | Highest accuracy but long runtime. |
|
1179
|
+
| | Requires bounds on translation. |
|
1180
|
+
+------------------------+-------------------------------------------+
|
1181
|
+
| basinhopping | Decent accuracy, medium runtime. |
|
1182
|
+
+------------------------+-------------------------------------------+
|
1183
|
+
| minimize | If initial values are closed to optimum |
|
1184
|
+
| | acceptable accuracy and short runtime |
|
1185
|
+
+------------------------+-------------------------------------------+
|
1173
1186
|
|
1174
1187
|
maxiter : int, optional
|
1175
|
-
The maximum number of iterations
|
1176
|
-
`optimization_method` 'minimize'.
|
1177
|
-
|
1188
|
+
The maximum number of iterations, 50 by default.
|
1178
1189
|
x0 : tuple of floats, optional
|
1179
|
-
Initial values for the optimizer,
|
1190
|
+
Initial values for the optimizer, zero by default.
|
1180
1191
|
|
1181
1192
|
Returns
|
1182
1193
|
-------
|
1183
1194
|
Tuple[ArrayLike, ArrayLike, float]
|
1184
|
-
|
1195
|
+
Optimal translation, rotation matrix and corresponding score.
|
1185
1196
|
|
1186
1197
|
Raises
|
1187
1198
|
------
|
1188
1199
|
ValueError
|
1189
|
-
If
|
1200
|
+
If ``optimization_method`` is not supported.
|
1190
1201
|
|
1191
1202
|
Notes
|
1192
1203
|
-----
|
1193
1204
|
This function currently only supports three-dimensional optimization and
|
1194
|
-
|
1205
|
+
``score_object`` will be modified during this operation.
|
1206
|
+
|
1207
|
+
Examples
|
1208
|
+
--------
|
1209
|
+
Having defined ``score_object``, for instance via :py:meth:`create_score_object`,
|
1210
|
+
non-exhaustive template matching can be performed as follows
|
1211
|
+
|
1212
|
+
>>> translation_fit, rotation_fit, score = optimize_match(score_object)
|
1213
|
+
|
1214
|
+
`translation_fit` and `rotation_fit` correspond to the inverse of the applied
|
1215
|
+
translation and rotation, so the following statements should hold within tolerance
|
1216
|
+
|
1217
|
+
>>> np.allclose(translation, -translation_fit, atol = 1) # True
|
1218
|
+
>>> np.allclose(rotation, np.linalg.inv(rotation_fit), rtol = .1) # True
|
1219
|
+
|
1220
|
+
Bounds on translation and rotation can be defined as follows
|
1221
|
+
|
1222
|
+
>>> translation_fit, rotation_fit, score = optimize_match(
|
1223
|
+
>>> score_object=score_object,
|
1224
|
+
>>> bounds_translation=((-5,5),(-2,2),(0,0)),
|
1225
|
+
>>> bounds_rotation=((-10,10), (-5,5), (0,0)),
|
1226
|
+
>>> )
|
1227
|
+
|
1228
|
+
The optimization scheme and the initial parameter estimates can also be adapted
|
1229
|
+
|
1230
|
+
>>> translation_fit, rotation_fit, score = optimize_match(
|
1231
|
+
>>> score_object=score_object,
|
1232
|
+
>>> optimization_method="minimize",
|
1233
|
+
>>> x0=(0,0,0,5,3,-5),
|
1234
|
+
>>> )
|
1235
|
+
|
1195
1236
|
"""
|
1196
1237
|
ndim = 3
|
1197
1238
|
_optimization_method = {
|
@@ -1262,7 +1303,3 @@ def optimize_match(
|
|
1262
1303
|
translation, rotation = result.x[:ndim], result.x[ndim:]
|
1263
1304
|
rotation_matrix = euler_to_rotationmatrix(rotation)
|
1264
1305
|
return translation, rotation_matrix, result.fun
|
1265
|
-
|
1266
|
-
|
1267
|
-
class FitRefinement:
|
1268
|
-
pass
|