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.
Files changed (40) hide show
  1. {pytme-0.2.0.data → pytme-0.2.1.data}/scripts/match_template.py +183 -69
  2. {pytme-0.2.0.data → pytme-0.2.1.data}/scripts/postprocess.py +107 -49
  3. {pytme-0.2.0.data → pytme-0.2.1.data}/scripts/preprocessor_gui.py +4 -1
  4. {pytme-0.2.0.dist-info → pytme-0.2.1.dist-info}/METADATA +1 -1
  5. pytme-0.2.1.dist-info/RECORD +73 -0
  6. scripts/extract_candidates.py +117 -85
  7. scripts/match_template.py +183 -69
  8. scripts/match_template_filters.py +193 -71
  9. scripts/postprocess.py +107 -49
  10. scripts/preprocessor_gui.py +4 -1
  11. scripts/refine_matches.py +364 -160
  12. tme/__version__.py +1 -1
  13. tme/analyzer.py +259 -117
  14. tme/backends/__init__.py +1 -0
  15. tme/backends/cupy_backend.py +20 -13
  16. tme/backends/jax_backend.py +218 -0
  17. tme/backends/matching_backend.py +25 -10
  18. tme/backends/mlx_backend.py +13 -9
  19. tme/backends/npfftw_backend.py +20 -8
  20. tme/backends/pytorch_backend.py +20 -9
  21. tme/density.py +79 -60
  22. tme/extensions.cpython-311-darwin.so +0 -0
  23. tme/matching_data.py +85 -61
  24. tme/matching_exhaustive.py +222 -129
  25. tme/matching_optimization.py +117 -76
  26. tme/orientations.py +175 -55
  27. tme/preprocessing/_utils.py +17 -5
  28. tme/preprocessing/composable_filter.py +2 -1
  29. tme/preprocessing/compose.py +1 -2
  30. tme/preprocessing/frequency_filters.py +97 -41
  31. tme/preprocessing/tilt_series.py +137 -87
  32. tme/preprocessor.py +3 -0
  33. tme/structure.py +4 -1
  34. pytme-0.2.0.dist-info/RECORD +0 -72
  35. {pytme-0.2.0.data → pytme-0.2.1.data}/scripts/estimate_ram_usage.py +0 -0
  36. {pytme-0.2.0.data → pytme-0.2.1.data}/scripts/preprocess.py +0 -0
  37. {pytme-0.2.0.dist-info → pytme-0.2.1.dist-info}/LICENSE +0 -0
  38. {pytme-0.2.0.dist-info → pytme-0.2.1.dist-info}/WHEEL +0 -0
  39. {pytme-0.2.0.dist-info → pytme-0.2.1.dist-info}/entry_points.txt +0 -0
  40. {pytme-0.2.0.dist-info → pytme-0.2.1.dist-info}/top_level.txt +0 -0
@@ -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
- target_pad = matching_data.target_padding(pad_target=pad_target_edges)
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
- fast_shape, fast_ft_shape, fourier_shift = matching_data.fourier_padding(
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
- fast_shape, backend._default_dtype
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.template_mask is None else rotate_mask
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.template_mask
113
+ self.template_mask = matching_data._template_mask
122
114
  self.template_mask_rot = backend.topleft_pad(
123
- matching_data.template_mask, fast_shape
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
- @staticmethod
132
- def rigid_transform(
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
- use_geometric_center: bool = False,
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
- grid = np.indices(arr.shape, dtype=np.float32).reshape(arr.ndim, -1)
148
- np.subtract(grid, center, out=grid)
149
- np.matmul(rotation_matrix.T, grid, out=grid)
150
- np.add(grid, center, out=grid)
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, grid, order=order, output=out.ravel())
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(arr_mask, grid, order=order, output=out_mask.ravel())
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": 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.rigid_transform(**kw_dict)
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
- ex2 = backend.sum(
579
- backend.divide(
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(backend.multiply(self.target, self.template_mask_rot)),
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
- try:
617
- score = np.dot(
618
- self.target_density[
619
- tuple(
620
- self.template_coordinates_rotated[:, self.in_volume].astype(int)
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 _post_init(self, **kwargs):
694
- target_norm = np.linalg.norm(self.target_density[self.target_density != 0])
695
- template_norm = np.linalg.norm(self.template_weights)
696
- self.denominator = np.fmax(target_norm * template_norm, np.finfo(float).eps)
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
- initial_score = score_object()
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=np.zeros(2 * ndim),
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=np.zeros(2 * ndim),
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}")