pytme 0.2.0__cp311-cp311-macosx_14_0_arm64.whl → 0.2.1__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.0.data → pytme-0.2.1.data}/scripts/match_template.py +183 -69
- {pytme-0.2.0.data → pytme-0.2.1.data}/scripts/postprocess.py +107 -49
- {pytme-0.2.0.data → pytme-0.2.1.data}/scripts/preprocessor_gui.py +4 -1
- {pytme-0.2.0.dist-info → pytme-0.2.1.dist-info}/METADATA +1 -1
- pytme-0.2.1.dist-info/RECORD +73 -0
- scripts/extract_candidates.py +117 -85
- scripts/match_template.py +183 -69
- scripts/match_template_filters.py +193 -71
- scripts/postprocess.py +107 -49
- scripts/preprocessor_gui.py +4 -1
- scripts/refine_matches.py +364 -160
- tme/__version__.py +1 -1
- tme/analyzer.py +259 -117
- tme/backends/__init__.py +1 -0
- tme/backends/cupy_backend.py +20 -13
- tme/backends/jax_backend.py +218 -0
- tme/backends/matching_backend.py +25 -10
- tme/backends/mlx_backend.py +13 -9
- tme/backends/npfftw_backend.py +20 -8
- tme/backends/pytorch_backend.py +20 -9
- tme/density.py +79 -60
- tme/extensions.cpython-311-darwin.so +0 -0
- tme/matching_data.py +85 -61
- tme/matching_exhaustive.py +222 -129
- tme/matching_optimization.py +117 -76
- tme/orientations.py +175 -55
- tme/preprocessing/_utils.py +17 -5
- tme/preprocessing/composable_filter.py +2 -1
- tme/preprocessing/compose.py +1 -2
- tme/preprocessing/frequency_filters.py +97 -41
- tme/preprocessing/tilt_series.py +137 -87
- tme/preprocessor.py +3 -0
- tme/structure.py +4 -1
- pytme-0.2.0.dist-info/RECORD +0 -72
- {pytme-0.2.0.data → pytme-0.2.1.data}/scripts/estimate_ram_usage.py +0 -0
- {pytme-0.2.0.data → pytme-0.2.1.data}/scripts/preprocess.py +0 -0
- {pytme-0.2.0.dist-info → pytme-0.2.1.dist-info}/LICENSE +0 -0
- {pytme-0.2.0.dist-info → pytme-0.2.1.dist-info}/WHEEL +0 -0
- {pytme-0.2.0.dist-info → pytme-0.2.1.dist-info}/entry_points.txt +0 -0
- {pytme-0.2.0.dist-info → pytme-0.2.1.dist-info}/top_level.txt +0 -0
tme/matching_optimization.py
CHANGED
@@ -100,36 +100,31 @@ class _MatchDensityToDensity(ABC):
|
|
100
100
|
if target_mask is not None:
|
101
101
|
matching_data.target_mask = target_mask
|
102
102
|
|
103
|
-
|
104
|
-
matching_data = matching_data.subset_by_slice(target_pad=target_pad)
|
103
|
+
self.target, self.target_mask = matching_data.target, matching_data.target_mask
|
105
104
|
|
106
|
-
|
107
|
-
pad_fourier=pad_fourier
|
108
|
-
)
|
109
|
-
|
110
|
-
self.target = backend.topleft_pad(matching_data.target, fast_shape)
|
111
|
-
self.target_mask = matching_data.target_mask
|
112
|
-
|
113
|
-
self.template = matching_data.template
|
105
|
+
self.template = matching_data._template
|
114
106
|
self.template_rot = backend.preallocate_array(
|
115
|
-
|
107
|
+
template.shape, backend._float_dtype
|
116
108
|
)
|
117
109
|
|
118
110
|
self.template_mask, self.template_mask_rot = 1, 1
|
119
|
-
rotate_mask = False if matching_data.
|
111
|
+
rotate_mask = False if matching_data._template_mask is None else rotate_mask
|
120
112
|
if matching_data.template_mask is not None:
|
121
|
-
self.template_mask = matching_data.
|
113
|
+
self.template_mask = matching_data._template_mask
|
122
114
|
self.template_mask_rot = backend.topleft_pad(
|
123
|
-
matching_data.
|
115
|
+
matching_data._template_mask, self.template_mask.shape
|
124
116
|
)
|
125
117
|
|
118
|
+
self.template_slices = tuple(slice(None) for _ in self.template.shape)
|
119
|
+
self.target_slices = tuple(slice(0, x) for x in self.template.shape)
|
120
|
+
|
126
121
|
self.score_sign = -1 if negate_score else 1
|
127
122
|
|
128
123
|
if hasattr(self, "_post_init"):
|
129
124
|
self._post_init(**kwargs)
|
130
125
|
|
131
|
-
|
132
|
-
|
126
|
+
def rotate_array(
|
127
|
+
self,
|
133
128
|
arr,
|
134
129
|
rotation_matrix,
|
135
130
|
translation,
|
@@ -137,28 +132,39 @@ class _MatchDensityToDensity(ABC):
|
|
137
132
|
out=None,
|
138
133
|
out_mask=None,
|
139
134
|
order: int = 1,
|
140
|
-
|
135
|
+
**kwargs,
|
141
136
|
):
|
142
137
|
rotate_mask = arr_mask is not None
|
143
138
|
return_type = (out is None) + 2 * rotate_mask * (out_mask is None)
|
144
139
|
translation = np.zeros(arr.ndim) if translation is None else translation
|
145
140
|
|
146
141
|
center = np.floor(np.array(arr.shape) / 2)[:, None]
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
142
|
+
|
143
|
+
if not hasattr(self, "_previous_center"):
|
144
|
+
self._previous_center = arr.shape
|
145
|
+
|
146
|
+
if not hasattr(self, "grid") or not np.allclose(self._previous_center, center):
|
147
|
+
self.grid = np.indices(arr.shape, dtype=np.float32).reshape(arr.ndim, -1)
|
148
|
+
np.subtract(self.grid, center, out=self.grid)
|
149
|
+
self.grid_out = np.zeros_like(self.grid)
|
150
|
+
self._previous_center = center
|
151
|
+
|
152
|
+
np.matmul(rotation_matrix.T, self.grid, out=self.grid_out)
|
153
|
+
translation = np.add(translation[:, None], center)
|
154
|
+
np.add(self.grid_out, translation, out=self.grid_out)
|
151
155
|
|
152
156
|
if out is None:
|
153
157
|
out = np.zeros_like(arr)
|
154
158
|
|
155
|
-
map_coordinates(arr,
|
159
|
+
map_coordinates(arr, self.grid_out, order=order, output=out.ravel())
|
156
160
|
|
157
161
|
if out_mask is None and arr_mask is not None:
|
158
162
|
out_mask = np.zeros_like(arr_mask)
|
159
163
|
|
160
164
|
if arr_mask is not None:
|
161
|
-
map_coordinates(
|
165
|
+
map_coordinates(
|
166
|
+
arr_mask, self.grid_out, order=order, output=out_mask.ravel()
|
167
|
+
)
|
162
168
|
|
163
169
|
match return_type:
|
164
170
|
case 0:
|
@@ -218,19 +224,52 @@ class _MatchDensityToDensity(ABC):
|
|
218
224
|
The matching score obtained for the transformation.
|
219
225
|
"""
|
220
226
|
translation, rotation_matrix = _format_rigid_transform(x)
|
227
|
+
self.template_rot.fill(0)
|
228
|
+
|
229
|
+
voxel_translation = backend.astype(translation, backend._int_dtype)
|
230
|
+
subvoxel_translation = backend.subtract(translation, voxel_translation)
|
231
|
+
|
232
|
+
center = backend.astype(
|
233
|
+
backend.divide(self.template.shape, 2), backend._int_dtype
|
234
|
+
)
|
235
|
+
right_pad = backend.subtract(self.template.shape, center)
|
236
|
+
|
237
|
+
translated_center = backend.add(voxel_translation, center)
|
238
|
+
|
239
|
+
target_starts = backend.subtract(translated_center, center)
|
240
|
+
target_stops = backend.add(translated_center, right_pad)
|
241
|
+
|
242
|
+
template_starts = backend.subtract(
|
243
|
+
backend.maximum(target_starts, 0), target_starts
|
244
|
+
)
|
245
|
+
template_stops = backend.subtract(
|
246
|
+
target_stops, backend.minimum(target_stops, self.target.shape)
|
247
|
+
)
|
248
|
+
template_stops = backend.subtract(self.template.shape, template_stops)
|
249
|
+
|
250
|
+
target_starts = backend.maximum(target_starts, 0)
|
251
|
+
target_stops = backend.minimum(target_stops, self.target.shape)
|
252
|
+
|
253
|
+
cand_start, cand_stop = template_starts.astype(int), template_stops.astype(int)
|
254
|
+
obs_start, obs_stop = target_starts.astype(int), target_stops.astype(int)
|
255
|
+
|
256
|
+
self.template_slices = tuple(slice(s, e) for s, e in zip(cand_start, cand_stop))
|
257
|
+
self.target_slices = tuple(slice(s, e) for s, e in zip(obs_start, obs_stop))
|
258
|
+
|
221
259
|
kw_dict = {
|
222
260
|
"arr": self.template,
|
223
261
|
"rotation_matrix": rotation_matrix,
|
224
|
-
"translation":
|
262
|
+
"translation": subvoxel_translation,
|
225
263
|
"out": self.template_rot,
|
226
|
-
"use_geometric_center": False,
|
227
264
|
"order": self.interpolation_order,
|
265
|
+
"use_geometric_center": True,
|
228
266
|
}
|
229
267
|
if self.rotate_mask:
|
268
|
+
self.template_mask_rot.fill(0)
|
230
269
|
kw_dict["arr_mask"] = self.template_mask
|
231
270
|
kw_dict["out_mask"] = self.template_mask_rot
|
232
271
|
|
233
|
-
self.
|
272
|
+
self.rotate_array(**kw_dict)
|
234
273
|
|
235
274
|
return self()
|
236
275
|
|
@@ -539,13 +578,6 @@ class FLC(_MatchDensityToDensity):
|
|
539
578
|
CC(f,g) = \\mathcal{F}^{-1}(\\mathcal{F}(f) \\cdot \\mathcal{F}(g)^*)
|
540
579
|
|
541
580
|
and Nm is the number of voxels within the template mask m.
|
542
|
-
|
543
|
-
References
|
544
|
-
----------
|
545
|
-
.. [1] W. Wan, S. Khavnekar, J. Wagner, P. Erdmann, and W. Baumeister
|
546
|
-
Microsc. Microanal. 26, 2516 (2020)
|
547
|
-
.. [2] T. Hrabe, Y. Chen, S. Pfeffer, L. Kuhn Cuellar, A.-V. Mangold,
|
548
|
-
and F. Förster, J. Struct. Biol. 178, 177 (2012).
|
549
581
|
"""
|
550
582
|
|
551
583
|
__doc__ += _MatchDensityToDensity.__doc__
|
@@ -562,9 +594,6 @@ class FLC(_MatchDensityToDensity):
|
|
562
594
|
mask_intensity=backend.sum(self.template_mask),
|
563
595
|
)
|
564
596
|
|
565
|
-
self.template = backend.reverse(self.template)
|
566
|
-
self.template_mask = backend.reverse(self.template_mask)
|
567
|
-
|
568
597
|
def __call__(self) -> float:
|
569
598
|
"""Returns the score of the current configuration."""
|
570
599
|
n_observations = backend.sum(self.template_mask_rot)
|
@@ -574,18 +603,29 @@ class FLC(_MatchDensityToDensity):
|
|
574
603
|
mask=self.template_mask_rot,
|
575
604
|
mask_intensity=n_observations,
|
576
605
|
)
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
backend.sum(
|
581
|
-
backend.multiply(self.target_square, self.template_mask_rot),
|
582
|
-
),
|
583
|
-
n_observations,
|
606
|
+
overlap = backend.sum(
|
607
|
+
backend.multiply(
|
608
|
+
self.template_rot[self.template_slices], self.target[self.target_slices]
|
584
609
|
)
|
585
610
|
)
|
611
|
+
|
612
|
+
ex2 = backend.divide(
|
613
|
+
backend.sum(
|
614
|
+
backend.multiply(
|
615
|
+
self.target_square[self.target_slices],
|
616
|
+
self.template_mask_rot[self.template_slices],
|
617
|
+
)
|
618
|
+
),
|
619
|
+
n_observations,
|
620
|
+
)
|
586
621
|
e2x = backend.square(
|
587
622
|
backend.divide(
|
588
|
-
backend.sum(
|
623
|
+
backend.sum(
|
624
|
+
backend.multiply(
|
625
|
+
self.target[self.target_slices],
|
626
|
+
self.template_mask_rot[self.template_slices],
|
627
|
+
)
|
628
|
+
),
|
589
629
|
n_observations,
|
590
630
|
)
|
591
631
|
)
|
@@ -594,8 +634,6 @@ class FLC(_MatchDensityToDensity):
|
|
594
634
|
denominator = backend.sqrt(denominator)
|
595
635
|
denominator = backend.multiply(denominator, n_observations)
|
596
636
|
|
597
|
-
overlap = backend.sum(backend.multiply(self.template_rot, self.target))
|
598
|
-
|
599
637
|
score = backend.divide(overlap, denominator) * self.score_sign
|
600
638
|
return score
|
601
639
|
|
@@ -613,29 +651,12 @@ class CrossCorrelation(_MatchCoordinatesToDensity):
|
|
613
651
|
|
614
652
|
def __call__(self) -> float:
|
615
653
|
"""Returns the score of the current configuration."""
|
616
|
-
|
617
|
-
|
618
|
-
self.
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
],
|
623
|
-
self.template_weights[self.in_volume],
|
624
|
-
)
|
625
|
-
except:
|
626
|
-
print(self.template_coordinates_rotated[:, self.in_volume].astype(int))
|
627
|
-
print(self.target_density.shape)
|
628
|
-
print(self.in_volume)
|
629
|
-
coordinates = self.template_coordinates_rotated[:, self.in_volume].astype(
|
630
|
-
int
|
631
|
-
)
|
632
|
-
in_volume = np.logical_and(
|
633
|
-
coordinates < np.array(self.target_density.shape)[:, None],
|
634
|
-
coordinates >= 0,
|
635
|
-
).min(axis=0)
|
636
|
-
print(in_volume)
|
637
|
-
|
638
|
-
raise ValueError()
|
654
|
+
score = np.dot(
|
655
|
+
self.target_density[
|
656
|
+
tuple(self.template_coordinates_rotated[:, self.in_volume].astype(int))
|
657
|
+
],
|
658
|
+
self.template_weights[self.in_volume],
|
659
|
+
)
|
639
660
|
score /= self.denominator
|
640
661
|
return score * self.score_sign
|
641
662
|
|
@@ -690,10 +711,21 @@ class NormalizedCrossCorrelation(CrossCorrelation):
|
|
690
711
|
|
691
712
|
__doc__ += _MatchCoordinatesToDensity.__doc__
|
692
713
|
|
693
|
-
def
|
694
|
-
|
695
|
-
|
696
|
-
|
714
|
+
def __call__(self) -> float:
|
715
|
+
n_observations = backend.sum(self.in_volume_mask)
|
716
|
+
target_coordinates = backend.astype(
|
717
|
+
self.template_mask_coordinates_rotated[:, self.in_volume_mask], int
|
718
|
+
)
|
719
|
+
target_weight = self.target_density[tuple(target_coordinates)]
|
720
|
+
ex2 = backend.divide(backend.sum(backend.square(target_weight)), n_observations)
|
721
|
+
e2x = backend.square(backend.divide(backend.sum(target_weight), n_observations))
|
722
|
+
|
723
|
+
denominator = backend.maximum(backend.subtract(ex2, e2x), 0.0)
|
724
|
+
denominator = backend.sqrt(denominator)
|
725
|
+
denominator = backend.multiply(denominator, n_observations)
|
726
|
+
self.denominator = denominator
|
727
|
+
|
728
|
+
return super().__call__()
|
697
729
|
|
698
730
|
|
699
731
|
class NormalizedCrossCorrelationMean(NormalizedCrossCorrelation):
|
@@ -1106,6 +1138,7 @@ def optimize_match(
|
|
1106
1138
|
bounds_rotation: Tuple[Tuple[float]] = None,
|
1107
1139
|
optimization_method: str = "basinhopping",
|
1108
1140
|
maxiter: int = 500,
|
1141
|
+
x0: Tuple[float] = None,
|
1109
1142
|
) -> Tuple[ArrayLike, ArrayLike, float]:
|
1110
1143
|
"""
|
1111
1144
|
Find the translation and rotation optimizing the score returned by `score_object`
|
@@ -1137,10 +1170,14 @@ def optimize_match(
|
|
1137
1170
|
| 'minimize' | If initial values are closed to optimum |
|
1138
1171
|
| | decent performance, short runtime. |
|
1139
1172
|
+--------------------------+-----------------------------------------+
|
1173
|
+
|
1140
1174
|
maxiter : int, optional
|
1141
1175
|
The maximum number of iterations. Default is 500. Not considered for
|
1142
1176
|
`optimization_method` 'minimize'.
|
1143
1177
|
|
1178
|
+
x0 : tuple of floats, optional
|
1179
|
+
Initial values for the optimizer, defaults to zero.
|
1180
|
+
|
1144
1181
|
Returns
|
1145
1182
|
-------
|
1146
1183
|
Tuple[ArrayLike, ArrayLike, float]
|
@@ -1191,10 +1228,12 @@ def optimize_match(
|
|
1191
1228
|
np.eye(len(bounds)), np.min(bounds, axis=1), np.max(bounds, axis=1)
|
1192
1229
|
)
|
1193
1230
|
|
1194
|
-
|
1231
|
+
x0 = np.zeros(2 * ndim) if x0 is None else x0
|
1232
|
+
|
1233
|
+
initial_score = score_object.score(x=x0)
|
1195
1234
|
if optimization_method == "basinhopping":
|
1196
1235
|
result = basinhopping(
|
1197
|
-
x0=
|
1236
|
+
x0=x0,
|
1198
1237
|
func=score_object.score,
|
1199
1238
|
niter=maxiter,
|
1200
1239
|
minimizer_kwargs={"method": "COBYLA", "constraints": linear_constraint},
|
@@ -1207,11 +1246,13 @@ def optimize_match(
|
|
1207
1246
|
maxiter=maxiter,
|
1208
1247
|
)
|
1209
1248
|
elif optimization_method == "minimize":
|
1249
|
+
print(maxiter)
|
1210
1250
|
result = minimize(
|
1211
|
-
x0=
|
1251
|
+
x0=x0,
|
1212
1252
|
fun=score_object.score,
|
1213
1253
|
bounds=bounds,
|
1214
1254
|
constraints=linear_constraint,
|
1255
|
+
options={"maxiter": maxiter},
|
1215
1256
|
)
|
1216
1257
|
print(f"Niter: {result.nit}, success : {result.success} ({result.message}).")
|
1217
1258
|
print(f"Initial score: {initial_score} - Refined score: {result.fun}")
|