pytme 0.2.0b0__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.0b0.data → pytme-0.2.1.data}/scripts/match_template.py +473 -140
- {pytme-0.2.0b0.data → pytme-0.2.1.data}/scripts/postprocess.py +107 -49
- {pytme-0.2.0b0.data → pytme-0.2.1.data}/scripts/preprocessor_gui.py +4 -1
- {pytme-0.2.0b0.dist-info → pytme-0.2.1.dist-info}/METADATA +2 -2
- pytme-0.2.1.dist-info/RECORD +73 -0
- scripts/extract_candidates.py +117 -85
- scripts/match_template.py +473 -140
- scripts/match_template_filters.py +458 -169
- 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 +278 -148
- 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 +22 -12
- tme/backends/pytorch_backend.py +20 -9
- tme/density.py +85 -64
- tme/extensions.cpython-311-darwin.so +0 -0
- tme/matching_data.py +86 -60
- tme/matching_exhaustive.py +245 -166
- tme/matching_optimization.py +137 -69
- tme/matching_utils.py +1 -1
- tme/orientations.py +175 -55
- tme/preprocessing/__init__.py +2 -0
- tme/preprocessing/_utils.py +188 -0
- tme/preprocessing/composable_filter.py +31 -0
- tme/preprocessing/compose.py +51 -0
- tme/preprocessing/frequency_filters.py +378 -0
- tme/preprocessing/tilt_series.py +1017 -0
- tme/preprocessor.py +17 -7
- tme/structure.py +4 -1
- pytme-0.2.0b0.dist-info/RECORD +0 -66
- {pytme-0.2.0b0.data → pytme-0.2.1.data}/scripts/estimate_ram_usage.py +0 -0
- {pytme-0.2.0b0.data → pytme-0.2.1.data}/scripts/preprocess.py +0 -0
- {pytme-0.2.0b0.dist-info → pytme-0.2.1.dist-info}/LICENSE +0 -0
- {pytme-0.2.0b0.dist-info → pytme-0.2.1.dist-info}/WHEEL +0 -0
- {pytme-0.2.0b0.dist-info → pytme-0.2.1.dist-info}/entry_points.txt +0 -0
- {pytme-0.2.0b0.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
|
|
@@ -521,6 +560,26 @@ class _MatchCoordinatesToCoordinates(_MatchDensityToDensity):
|
|
521
560
|
|
522
561
|
|
523
562
|
class FLC(_MatchDensityToDensity):
|
563
|
+
"""
|
564
|
+
Computes a normalized cross-correlation score of a target f a template g
|
565
|
+
and a mask m:
|
566
|
+
|
567
|
+
.. math::
|
568
|
+
|
569
|
+
\\frac{CC(f, \\frac{g*m - \\overline{g*m}}{\\sigma_{g*m}})}
|
570
|
+
{N_m * \\sqrt{
|
571
|
+
\\frac{CC(f^2, m)}{N_m} - (\\frac{CC(f, m)}{N_m})^2}
|
572
|
+
}
|
573
|
+
|
574
|
+
Where:
|
575
|
+
|
576
|
+
.. math::
|
577
|
+
|
578
|
+
CC(f,g) = \\mathcal{F}^{-1}(\\mathcal{F}(f) \\cdot \\mathcal{F}(g)^*)
|
579
|
+
|
580
|
+
and Nm is the number of voxels within the template mask m.
|
581
|
+
"""
|
582
|
+
|
524
583
|
__doc__ += _MatchDensityToDensity.__doc__
|
525
584
|
|
526
585
|
def _post_init(self, **kwargs: Dict):
|
@@ -535,9 +594,6 @@ class FLC(_MatchDensityToDensity):
|
|
535
594
|
mask_intensity=backend.sum(self.template_mask),
|
536
595
|
)
|
537
596
|
|
538
|
-
self.template = backend.reverse(self.template)
|
539
|
-
self.template_mask = backend.reverse(self.template_mask)
|
540
|
-
|
541
597
|
def __call__(self) -> float:
|
542
598
|
"""Returns the score of the current configuration."""
|
543
599
|
n_observations = backend.sum(self.template_mask_rot)
|
@@ -547,18 +603,29 @@ class FLC(_MatchDensityToDensity):
|
|
547
603
|
mask=self.template_mask_rot,
|
548
604
|
mask_intensity=n_observations,
|
549
605
|
)
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
backend.sum(
|
554
|
-
backend.multiply(self.target_square, self.template_mask_rot),
|
555
|
-
),
|
556
|
-
n_observations,
|
606
|
+
overlap = backend.sum(
|
607
|
+
backend.multiply(
|
608
|
+
self.template_rot[self.template_slices], self.target[self.target_slices]
|
557
609
|
)
|
558
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
|
+
)
|
559
621
|
e2x = backend.square(
|
560
622
|
backend.divide(
|
561
|
-
backend.sum(
|
623
|
+
backend.sum(
|
624
|
+
backend.multiply(
|
625
|
+
self.target[self.target_slices],
|
626
|
+
self.template_mask_rot[self.template_slices],
|
627
|
+
)
|
628
|
+
),
|
562
629
|
n_observations,
|
563
630
|
)
|
564
631
|
)
|
@@ -567,8 +634,6 @@ class FLC(_MatchDensityToDensity):
|
|
567
634
|
denominator = backend.sqrt(denominator)
|
568
635
|
denominator = backend.multiply(denominator, n_observations)
|
569
636
|
|
570
|
-
overlap = backend.sum(backend.multiply(self.template_rot, self.target))
|
571
|
-
|
572
637
|
score = backend.divide(overlap, denominator) * self.score_sign
|
573
638
|
return score
|
574
639
|
|
@@ -586,29 +651,12 @@ class CrossCorrelation(_MatchCoordinatesToDensity):
|
|
586
651
|
|
587
652
|
def __call__(self) -> float:
|
588
653
|
"""Returns the score of the current configuration."""
|
589
|
-
|
590
|
-
|
591
|
-
self.
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
],
|
596
|
-
self.template_weights[self.in_volume],
|
597
|
-
)
|
598
|
-
except:
|
599
|
-
print(self.template_coordinates_rotated[:, self.in_volume].astype(int))
|
600
|
-
print(self.target_density.shape)
|
601
|
-
print(self.in_volume)
|
602
|
-
coordinates = self.template_coordinates_rotated[:, self.in_volume].astype(
|
603
|
-
int
|
604
|
-
)
|
605
|
-
in_volume = np.logical_and(
|
606
|
-
coordinates < np.array(self.target_density.shape)[:, None],
|
607
|
-
coordinates >= 0,
|
608
|
-
).min(axis=0)
|
609
|
-
print(in_volume)
|
610
|
-
|
611
|
-
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
|
+
)
|
612
660
|
score /= self.denominator
|
613
661
|
return score * self.score_sign
|
614
662
|
|
@@ -663,10 +711,21 @@ class NormalizedCrossCorrelation(CrossCorrelation):
|
|
663
711
|
|
664
712
|
__doc__ += _MatchCoordinatesToDensity.__doc__
|
665
713
|
|
666
|
-
def
|
667
|
-
|
668
|
-
|
669
|
-
|
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__()
|
670
729
|
|
671
730
|
|
672
731
|
class NormalizedCrossCorrelationMean(NormalizedCrossCorrelation):
|
@@ -1079,6 +1138,7 @@ def optimize_match(
|
|
1079
1138
|
bounds_rotation: Tuple[Tuple[float]] = None,
|
1080
1139
|
optimization_method: str = "basinhopping",
|
1081
1140
|
maxiter: int = 500,
|
1141
|
+
x0: Tuple[float] = None,
|
1082
1142
|
) -> Tuple[ArrayLike, ArrayLike, float]:
|
1083
1143
|
"""
|
1084
1144
|
Find the translation and rotation optimizing the score returned by `score_object`
|
@@ -1110,10 +1170,14 @@ def optimize_match(
|
|
1110
1170
|
| 'minimize' | If initial values are closed to optimum |
|
1111
1171
|
| | decent performance, short runtime. |
|
1112
1172
|
+--------------------------+-----------------------------------------+
|
1173
|
+
|
1113
1174
|
maxiter : int, optional
|
1114
1175
|
The maximum number of iterations. Default is 500. Not considered for
|
1115
1176
|
`optimization_method` 'minimize'.
|
1116
1177
|
|
1178
|
+
x0 : tuple of floats, optional
|
1179
|
+
Initial values for the optimizer, defaults to zero.
|
1180
|
+
|
1117
1181
|
Returns
|
1118
1182
|
-------
|
1119
1183
|
Tuple[ArrayLike, ArrayLike, float]
|
@@ -1164,10 +1228,12 @@ def optimize_match(
|
|
1164
1228
|
np.eye(len(bounds)), np.min(bounds, axis=1), np.max(bounds, axis=1)
|
1165
1229
|
)
|
1166
1230
|
|
1167
|
-
|
1231
|
+
x0 = np.zeros(2 * ndim) if x0 is None else x0
|
1232
|
+
|
1233
|
+
initial_score = score_object.score(x=x0)
|
1168
1234
|
if optimization_method == "basinhopping":
|
1169
1235
|
result = basinhopping(
|
1170
|
-
x0=
|
1236
|
+
x0=x0,
|
1171
1237
|
func=score_object.score,
|
1172
1238
|
niter=maxiter,
|
1173
1239
|
minimizer_kwargs={"method": "COBYLA", "constraints": linear_constraint},
|
@@ -1180,11 +1246,13 @@ def optimize_match(
|
|
1180
1246
|
maxiter=maxiter,
|
1181
1247
|
)
|
1182
1248
|
elif optimization_method == "minimize":
|
1249
|
+
print(maxiter)
|
1183
1250
|
result = minimize(
|
1184
|
-
x0=
|
1251
|
+
x0=x0,
|
1185
1252
|
fun=score_object.score,
|
1186
1253
|
bounds=bounds,
|
1187
1254
|
constraints=linear_constraint,
|
1255
|
+
options={"maxiter": maxiter},
|
1188
1256
|
)
|
1189
1257
|
print(f"Niter: {result.nit}, success : {result.success} ({result.message}).")
|
1190
1258
|
print(f"Initial score: {initial_score} - Refined score: {result.fun}")
|
tme/matching_utils.py
CHANGED
@@ -686,7 +686,7 @@ def get_rotations_around_vector(
|
|
686
686
|
|
687
687
|
# phi, theta, psi
|
688
688
|
axis_angle /= n_symmetry
|
689
|
-
phi_steps = np.
|
689
|
+
phi_steps = np.maximum(np.round(axis_angle / axis_sampling), 1).astype(int)
|
690
690
|
phi = np.linspace(0, axis_angle, phi_steps + 1)[:-1]
|
691
691
|
np.add(phi, angles_vector.as_euler("zyx", degrees=True)[0], out=phi)
|
692
692
|
angles = np.stack(
|