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.
Files changed (52) hide show
  1. {pytme-0.2.1.data → pytme-0.2.3.data}/scripts/match_template.py +219 -216
  2. {pytme-0.2.1.data → pytme-0.2.3.data}/scripts/postprocess.py +86 -54
  3. pytme-0.2.3.data/scripts/preprocess.py +132 -0
  4. {pytme-0.2.1.data → pytme-0.2.3.data}/scripts/preprocessor_gui.py +181 -94
  5. pytme-0.2.3.dist-info/METADATA +92 -0
  6. pytme-0.2.3.dist-info/RECORD +75 -0
  7. {pytme-0.2.1.dist-info → pytme-0.2.3.dist-info}/WHEEL +1 -1
  8. pytme-0.2.1.data/scripts/preprocess.py → scripts/eval.py +1 -1
  9. scripts/extract_candidates.py +20 -13
  10. scripts/match_template.py +219 -216
  11. scripts/match_template_filters.py +154 -95
  12. scripts/postprocess.py +86 -54
  13. scripts/preprocess.py +95 -56
  14. scripts/preprocessor_gui.py +181 -94
  15. scripts/refine_matches.py +265 -61
  16. tme/__init__.py +0 -1
  17. tme/__version__.py +1 -1
  18. tme/analyzer.py +458 -813
  19. tme/backends/__init__.py +40 -11
  20. tme/backends/_jax_utils.py +187 -0
  21. tme/backends/cupy_backend.py +109 -226
  22. tme/backends/jax_backend.py +230 -152
  23. tme/backends/matching_backend.py +445 -384
  24. tme/backends/mlx_backend.py +32 -59
  25. tme/backends/npfftw_backend.py +240 -507
  26. tme/backends/pytorch_backend.py +30 -151
  27. tme/density.py +248 -371
  28. tme/extensions.cpython-311-darwin.so +0 -0
  29. tme/matching_data.py +328 -284
  30. tme/matching_exhaustive.py +195 -1499
  31. tme/matching_optimization.py +143 -106
  32. tme/matching_scores.py +887 -0
  33. tme/matching_utils.py +287 -388
  34. tme/memory.py +377 -0
  35. tme/orientations.py +78 -21
  36. tme/parser.py +3 -4
  37. tme/preprocessing/_utils.py +61 -32
  38. tme/preprocessing/composable_filter.py +7 -4
  39. tme/preprocessing/compose.py +7 -3
  40. tme/preprocessing/frequency_filters.py +49 -39
  41. tme/preprocessing/tilt_series.py +44 -72
  42. tme/preprocessor.py +560 -526
  43. tme/structure.py +491 -188
  44. tme/types.py +5 -3
  45. pytme-0.2.1.dist-info/METADATA +0 -73
  46. pytme-0.2.1.dist-info/RECORD +0 -73
  47. tme/helpers.py +0 -881
  48. tme/matching_constrained.py +0 -195
  49. {pytme-0.2.1.data → pytme-0.2.3.data}/scripts/estimate_ram_usage.py +0 -0
  50. {pytme-0.2.1.dist-info → pytme-0.2.3.dist-info}/LICENSE +0 -0
  51. {pytme-0.2.1.dist-info → pytme-0.2.3.dist-info}/entry_points.txt +0 -0
  52. {pytme-0.2.1.dist-info → pytme-0.2.3.dist-info}/top_level.txt +0 -0
@@ -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 numpy.typing import NDArray
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 .types import ArrayLike
24
- from .backends import backend
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 rigid_transform, euler_to_rotationmatrix
27
- from .matching_exhaustive import normalize_under_mask
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 = backend.to_backend_array(translation)
49
- rotation_matrix = euler_to_rotationmatrix(backend.to_numpy_array(angles))
50
- rotation_matrix = backend.to_backend_array(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 = backend.preallocate_array(
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 = backend.topleft_pad(
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 = backend.astype(translation, backend._int_dtype)
230
- subvoxel_translation = backend.subtract(translation, voxel_translation)
230
+ voxel_translation = be.astype(translation, be._int_dtype)
231
+ subvoxel_translation = be.subtract(translation, voxel_translation)
231
232
 
232
- center = backend.astype(
233
- backend.divide(self.template.shape, 2), backend._int_dtype
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 = backend.add(voxel_translation, center)
236
+ translated_center = be.add(voxel_translation, center)
238
237
 
239
- target_starts = backend.subtract(translated_center, center)
240
- target_stops = backend.add(translated_center, right_pad)
238
+ target_starts = be.subtract(translated_center, center)
239
+ target_stops = be.add(translated_center, right_pad)
241
240
 
242
- template_starts = backend.subtract(
243
- backend.maximum(target_starts, 0), target_starts
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 = backend.subtract(
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 = backend.maximum(target_starts, 0)
251
- target_stops = backend.minimum(target_stops, self.target.shape)
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 [d x N].
285
+ Template coordinate array with shape (d,n).
289
286
  template_weights : NDArray
290
- Template weight array with shape [N].
287
+ Template weight array with shape (n,).
291
288
  template_mask_coordinates : NDArray, optional
292
- Template mask coordinates with shape [d x N].
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=backend.zeros(target.ndim),
333
+ array_origin=be.zeros(target.ndim),
334
334
  array_shape=self.target_density.shape,
335
- sampling_rate=backend.full(target.ndim, fill_value=1),
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=backend.zeros(rotation_matrix.shape[0]),
371
+ array_origin=be.zeros(rotation_matrix.shape[0]),
372
372
  array_shape=self.target_density.shape,
373
- sampling_rate=backend.full(rotation_matrix.shape[0], fill_value=1),
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
- backend.multiply(self.target, self.target_mask, out=self.target)
587
+ be.multiply(self.target, self.target_mask, out=self.target)
588
588
 
589
- self.target_square = backend.square(self.target)
589
+ self.target_square = be.square(self.target)
590
590
 
591
- normalize_under_mask(
591
+ normalize_template(
592
592
  template=self.template,
593
593
  mask=self.template_mask,
594
- mask_intensity=backend.sum(self.template_mask),
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
- n_observations = backend.sum(self.template_mask_rot)
599
+ n_obs = be.sum(self.template_mask_rot)
600
600
 
601
- normalize_under_mask(
601
+ normalize_template(
602
602
  template=self.template_rot,
603
603
  mask=self.template_mask_rot,
604
- mask_intensity=n_observations,
604
+ n_observations=n_obs,
605
605
  )
606
- overlap = backend.sum(
607
- backend.multiply(
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
- 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
- )
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 = backend.maximum(backend.subtract(ex2, e2x), 0.0)
634
- denominator = backend.sqrt(denominator)
635
- denominator = backend.multiply(denominator, n_observations)
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 = backend.divide(overlap, denominator) * self.score_sign
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 = backend.sum(self.in_volume_mask)
716
- target_coordinates = backend.astype(
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 = backend.divide(backend.sum(backend.square(target_weight)), n_observations)
721
- e2x = backend.square(backend.divide(backend.sum(target_weight), n_observations))
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 = backend.maximum(backend.subtract(ex2, e2x), 0.0)
724
- denominator = backend.sqrt(denominator)
725
- denominator = backend.multiply(denominator, n_observations)
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 `**kwargs``.
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 = 500,
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 `score_object`
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 basinhopping. For further
1174
+ Optimizer that will be used, basinhopping by default. For further
1162
1175
  information refer to :doc:`scipy:reference/optimize`.
1163
1176
 
1164
- +--------------------------+-----------------------------------------+
1165
- | 'differential_evolution' | Highest accuracy but long runtime. |
1166
- | | Requires bounds on translation. |
1167
- +--------------------------+-----------------------------------------+
1168
- | 'basinhopping' | Decent accuracy, medium runtime. |
1169
- +--------------------------+-----------------------------------------+
1170
- | 'minimize' | If initial values are closed to optimum |
1171
- | | decent performance, short runtime. |
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. Default is 500. Not considered for
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, defaults to zero.
1190
+ Initial values for the optimizer, zero by default.
1180
1191
 
1181
1192
  Returns
1182
1193
  -------
1183
1194
  Tuple[ArrayLike, ArrayLike, float]
1184
- Translation and rotation matrix yielding final score.
1195
+ Optimal translation, rotation matrix and corresponding score.
1185
1196
 
1186
1197
  Raises
1187
1198
  ------
1188
1199
  ValueError
1189
- If `optimization_method` is not supported.
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
- `score_object` will be modified during this operation.
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