leopard-em 0.0.2a0__py3-none-any.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.
leopard_em/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ """Two-Dimensional Template Matching (2DTM) written in Python."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("leopard_em")
7
+ except PackageNotFoundError:
8
+ __version__ = "uninstalled"
9
+
10
+ __author__ = ["Josh Dickerson", "Matthew Giammar"]
11
+ __email__ = ["jdickerson@berkeley.edu", "matthew_giammar@berkeley.edu"]
@@ -0,0 +1 @@
1
+ """Submodule for analyzing results during the template matching pipeline."""
@@ -0,0 +1,214 @@
1
+ """Locates peaks in the scaled mip from a match template result."""
2
+
3
+ from typing import NamedTuple, Optional
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ import torch
8
+ from scipy.special import erfcinv
9
+
10
+
11
+ class MatchTemplatePeaks(NamedTuple):
12
+ """Helper class for return value of extract_peaks_and_statistics."""
13
+
14
+ pos_y: torch.Tensor
15
+ pos_x: torch.Tensor
16
+ mip: torch.Tensor
17
+ scaled_mip: torch.Tensor
18
+ psi: torch.Tensor
19
+ theta: torch.Tensor
20
+ phi: torch.Tensor
21
+ relative_defocus: torch.Tensor
22
+ correlation_mean: torch.Tensor
23
+ correlation_variance: torch.Tensor
24
+ total_correlations: int
25
+
26
+
27
+ def match_template_peaks_to_dict(peaks: MatchTemplatePeaks) -> dict:
28
+ """Convert MatchTemplatePeaks object to a dictionary."""
29
+ return peaks._asdict()
30
+
31
+
32
+ def match_template_peaks_to_dataframe(peaks: MatchTemplatePeaks) -> pd.DataFrame:
33
+ """Convert MatchTemplatePeaks object to a pandas DataFrame."""
34
+ return pd.DataFrame(peaks._asdict())
35
+
36
+
37
+ def gaussian_noise_zscore_cutoff(num_ccg: int, false_positives: float = 1.0) -> float:
38
+ """Determines the z-score cutoff based on Gaussian noise model and number of pixels.
39
+
40
+ NOTE: This procedure assumes that the z-scores (normalized maximum intensity
41
+ projections) are distributed according to a standard normal distribution. Here,
42
+ this model is used to find the cutoff value such that there is at most
43
+ 'false_positives' number of false positives in all of the pixels.
44
+
45
+ Parameters
46
+ ----------
47
+ num_ccg : int
48
+ Total number of cross-correlograms calculated during template matching. Product
49
+ of the number of pixels, number of defocus values, and number of orientations.
50
+ false_positives : float, optional
51
+ Number of false positives to allow in the image (over all pixels). Default is
52
+ 1.0 which corresponds to a single false-positive.
53
+
54
+ Returns
55
+ -------
56
+ float
57
+ Z-score cutoff.
58
+ """
59
+ tmp = erfcinv(2.0 * false_positives / num_ccg)
60
+ tmp *= np.sqrt(2.0)
61
+
62
+ return float(tmp)
63
+
64
+
65
+ def find_peaks_in_zscore(
66
+ zscore_map: torch.Tensor, zscore_cutoff: float, mask_radius: Optional[float] = 5.0
67
+ ) -> tuple[torch.Tensor, torch.Tensor]:
68
+ """Finds locations of peaks above a threshold using masking around each found peak.
69
+
70
+ Parameters
71
+ ----------
72
+ zscore_map : torch.Tensor
73
+ 2D tensor of z-scores.
74
+ zscore_cutoff : float
75
+ Z-score cutoff value.
76
+ mask_radius : float, optional
77
+ Radius of the circular mask to apply around the peak. Default is 5.0.
78
+
79
+ Returns
80
+ -------
81
+ tuple[torch.Tensor, torch.Tensor]
82
+ Tensors corresponding to the x and y coordinates of the peaks, respectively.
83
+ """
84
+ # Short circuit if the cutoff is too high
85
+ if zscore_cutoff > zscore_map.max():
86
+ return torch.tensor([]), torch.tensor([])
87
+
88
+ zscore_map_copy = zscore_map.clone()
89
+ H, W = zscore_map.shape
90
+
91
+ # Convert to next highest integer
92
+ mask_width = np.ceil(mask_radius).astype(int) * 2 + 1
93
+ mask = torch.ones(mask_width, mask_width)
94
+ x = torch.arange(mask_width) - mask_width // 2
95
+ y = torch.arange(mask_width) - mask_width // 2
96
+ xx, yy = torch.meshgrid(x, y, indexing="ij")
97
+ dist = torch.sqrt(xx**2 + yy**2)
98
+ mask[dist <= mask_radius] = 0.0
99
+ mask_radius = np.ceil(mask_radius).astype(int)
100
+
101
+ found_peaks_x = []
102
+ found_peaks_y = []
103
+ # Iteratively find the highest peak in the map, and then mask the surrounding region
104
+ while zscore_map_copy.max() >= zscore_cutoff:
105
+ peak_loc = torch.argmax(zscore_map_copy)
106
+ peak_loc = torch.unravel_index(peak_loc, zscore_map_copy.shape)
107
+ peak_x, peak_y = peak_loc
108
+ found_peaks_x.append(peak_x)
109
+ found_peaks_y.append(peak_y)
110
+
111
+ # Mask the region around the peak, taking into account image bounds
112
+ start_x = max(0, peak_x - mask_radius)
113
+ end_x = min(H, peak_x + mask_radius + 1)
114
+ start_y = max(0, peak_y - mask_radius)
115
+ end_y = min(W, peak_y + mask_radius + 1)
116
+
117
+ # Calculate the valid range of the mask to apply
118
+ mask_start_x = max(0, mask_radius - peak_x)
119
+ mask_end_x = mask_width - max(0, (peak_x + mask_radius + 1) - H)
120
+ mask_start_y = max(0, mask_radius - peak_y)
121
+ mask_end_y = mask_width - max(0, (peak_y + mask_radius + 1) - W)
122
+
123
+ zscore_map_copy[start_x:end_x, start_y:end_y] *= mask[
124
+ mask_start_x:mask_end_x, mask_start_y:mask_end_y
125
+ ]
126
+
127
+ found_peaks_x = torch.tensor(found_peaks_x)
128
+ found_peaks_y = torch.tensor(found_peaks_y)
129
+
130
+ return found_peaks_x, found_peaks_y
131
+
132
+
133
+ def extract_peaks_and_statistics(
134
+ mip: torch.Tensor,
135
+ scaled_mip: torch.Tensor,
136
+ best_psi: torch.Tensor,
137
+ best_theta: torch.Tensor,
138
+ best_phi: torch.Tensor,
139
+ best_defocus: torch.Tensor,
140
+ correlation_average: torch.Tensor,
141
+ correlation_variance: torch.Tensor,
142
+ total_correlation_positions: int,
143
+ z_score_cutoff: Optional[float] = None,
144
+ mask_radius: Optional[float] = 5.0,
145
+ ) -> MatchTemplatePeaks:
146
+ """Returns peak locations, heights, and pose stats from match template results.
147
+
148
+ Parameters
149
+ ----------
150
+ mip : torch.Tensor
151
+ Maximum intensity projection of the match template results.
152
+ scaled_mip : torch.Tensor
153
+ Scaled maximum intensity projection of the match template results.
154
+ best_psi : torch.Tensor
155
+ Best psi angles for each pixel.
156
+ best_theta : torch.Tensor
157
+ Best theta angles for each pixel.
158
+ best_phi : torch.Tensor
159
+ Best phi angles for each pixel.
160
+ best_defocus : torch.Tensor
161
+ Best relative defocus values for each pixel.
162
+ correlation_average : torch.Tensor
163
+ Average correlation value for each pixel.
164
+ correlation_variance : torch.Tensor
165
+ Variance of the correlation values for each pixel.
166
+ total_correlation_positions : int
167
+ Total number of correlation positions calculated during template matching. Must
168
+ be provided if `z_score_cutoff` is not provided (needed for the noise model).
169
+ z_score_cutoff : float, optional
170
+ Z-score cutoff value for peak detection. If not provided, it is calculated using
171
+ the Gaussian noise model. Default is None.
172
+ mask_radius : float, optional
173
+ Radius of the mask to apply around the peak, in units of pixels. Default is 5.0.
174
+
175
+ Returns
176
+ -------
177
+ MatchTemplatePeaks
178
+ Named tuple containing the peak locations, heights, and pose statistics.
179
+ """
180
+ if z_score_cutoff is None:
181
+ z_score_cutoff = gaussian_noise_zscore_cutoff(
182
+ mip.numel() * total_correlation_positions
183
+ )
184
+
185
+ # Find the peak locations only in the scaled MIP
186
+ pos_y, pos_x = find_peaks_in_zscore(scaled_mip, z_score_cutoff, mask_radius)
187
+
188
+ # rase error if no peaks are found
189
+ if len(pos_y) == 0:
190
+ raise ValueError("No peaks found in scaled MIP.")
191
+
192
+ # Extract peak heights, orientations, etc. from other maps
193
+ mip_peaks = mip[pos_y, pos_x]
194
+ scaled_mip_peaks = scaled_mip[pos_y, pos_x]
195
+ psi_peaks = best_psi[pos_y, pos_x]
196
+ theta_peaks = best_theta[pos_y, pos_x]
197
+ phi_peaks = best_phi[pos_y, pos_x]
198
+ relative_defocus_peaks = best_defocus[pos_y, pos_x]
199
+ correlation_average_peaks = correlation_average[pos_y, pos_x]
200
+ correlation_variance_peaks = correlation_variance[pos_y, pos_x]
201
+
202
+ return MatchTemplatePeaks(
203
+ pos_y=pos_y,
204
+ pos_x=pos_x,
205
+ mip=mip_peaks,
206
+ scaled_mip=scaled_mip_peaks,
207
+ psi=psi_peaks,
208
+ theta=theta_peaks,
209
+ phi=phi_peaks,
210
+ relative_defocus=relative_defocus_peaks,
211
+ correlation_mean=correlation_average_peaks,
212
+ correlation_variance=correlation_variance_peaks,
213
+ total_correlations=total_correlation_positions,
214
+ )
@@ -0,0 +1,10 @@
1
+ """Submodule for computationally intensive backend functions."""
2
+
3
+ from .core_match_template import core_match_template
4
+ from .core_refine_template import core_refine_template, cross_correlate_particle_stack
5
+
6
+ __all__ = [
7
+ "core_match_template",
8
+ "core_refine_template",
9
+ "cross_correlate_particle_stack",
10
+ ]