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.
Files changed (42) hide show
  1. {pytme-0.2.0b0.data → pytme-0.2.1.data}/scripts/match_template.py +473 -140
  2. {pytme-0.2.0b0.data → pytme-0.2.1.data}/scripts/postprocess.py +107 -49
  3. {pytme-0.2.0b0.data → pytme-0.2.1.data}/scripts/preprocessor_gui.py +4 -1
  4. {pytme-0.2.0b0.dist-info → pytme-0.2.1.dist-info}/METADATA +2 -2
  5. pytme-0.2.1.dist-info/RECORD +73 -0
  6. scripts/extract_candidates.py +117 -85
  7. scripts/match_template.py +473 -140
  8. scripts/match_template_filters.py +458 -169
  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 +278 -148
  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 +22 -12
  20. tme/backends/pytorch_backend.py +20 -9
  21. tme/density.py +85 -64
  22. tme/extensions.cpython-311-darwin.so +0 -0
  23. tme/matching_data.py +86 -60
  24. tme/matching_exhaustive.py +245 -166
  25. tme/matching_optimization.py +137 -69
  26. tme/matching_utils.py +1 -1
  27. tme/orientations.py +175 -55
  28. tme/preprocessing/__init__.py +2 -0
  29. tme/preprocessing/_utils.py +188 -0
  30. tme/preprocessing/composable_filter.py +31 -0
  31. tme/preprocessing/compose.py +51 -0
  32. tme/preprocessing/frequency_filters.py +378 -0
  33. tme/preprocessing/tilt_series.py +1017 -0
  34. tme/preprocessor.py +17 -7
  35. tme/structure.py +4 -1
  36. pytme-0.2.0b0.dist-info/RECORD +0 -66
  37. {pytme-0.2.0b0.data → pytme-0.2.1.data}/scripts/estimate_ram_usage.py +0 -0
  38. {pytme-0.2.0b0.data → pytme-0.2.1.data}/scripts/preprocess.py +0 -0
  39. {pytme-0.2.0b0.dist-info → pytme-0.2.1.dist-info}/LICENSE +0 -0
  40. {pytme-0.2.0b0.dist-info → pytme-0.2.1.dist-info}/WHEEL +0 -0
  41. {pytme-0.2.0b0.dist-info → pytme-0.2.1.dist-info}/entry_points.txt +0 -0
  42. {pytme-0.2.0b0.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
 
@@ -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
- ex2 = backend.sum(
552
- backend.divide(
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(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
+ ),
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
- try:
590
- score = np.dot(
591
- self.target_density[
592
- tuple(
593
- self.template_coordinates_rotated[:, self.in_volume].astype(int)
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 _post_init(self, **kwargs):
667
- target_norm = np.linalg.norm(self.target_density[self.target_density != 0])
668
- template_norm = np.linalg.norm(self.template_weights)
669
- 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__()
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
- 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)
1168
1234
  if optimization_method == "basinhopping":
1169
1235
  result = basinhopping(
1170
- x0=np.zeros(2 * ndim),
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=np.zeros(2 * ndim),
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.max(np.round(axis_angle / axis_sampling), 1)
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(