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 +11 -0
- leopard_em/analysis/__init__.py +1 -0
- leopard_em/analysis/pick_match_template_peaks.py +214 -0
- leopard_em/backend/__init__.py +10 -0
- leopard_em/backend/core_match_template.py +553 -0
- leopard_em/backend/core_refine_template.py +429 -0
- leopard_em/backend/process_results.py +179 -0
- leopard_em/backend/utils.py +163 -0
- leopard_em/pydantic_models/__init__.py +36 -0
- leopard_em/pydantic_models/computational_config.py +60 -0
- leopard_em/pydantic_models/correlation_filters.py +412 -0
- leopard_em/pydantic_models/defocus_search.py +72 -0
- leopard_em/pydantic_models/formats.py +88 -0
- leopard_em/pydantic_models/match_template_manager.py +411 -0
- leopard_em/pydantic_models/match_template_result.py +339 -0
- leopard_em/pydantic_models/optics_group.py +91 -0
- leopard_em/pydantic_models/orientation_search.py +163 -0
- leopard_em/pydantic_models/particle_stack.py +305 -0
- leopard_em/pydantic_models/refine_template_manager.py +288 -0
- leopard_em/pydantic_models/types.py +116 -0
- leopard_em/utils/cross_correlation.py +331 -0
- leopard_em/utils/data_io.py +154 -0
- leopard_em/utils/fourier_slice.py +135 -0
- leopard_em/utils/particle_stack.py +160 -0
- leopard_em/utils/pre_processing.py +134 -0
- leopard_em-0.0.2a0.dist-info/METADATA +135 -0
- leopard_em-0.0.2a0.dist-info/RECORD +31 -0
- leopard_em-0.0.2a0.dist-info/WHEEL +4 -0
- leopard_em-0.0.2a0.dist-info/licenses/LICENSE +28 -0
- programs/match_template.py +5 -0
- programs/refine_template.py +5 -0
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
|
+
]
|