lattice-sub 1.2.2__tar.gz → 1.3.1__tar.gz
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.
- {lattice_sub-1.2.2/src/lattice_sub.egg-info → lattice_sub-1.3.1}/PKG-INFO +15 -3
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/README.md +14 -2
- lattice_sub-1.3.1/docs/images/example_comparison.png +0 -0
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/examples/config.yaml +2 -2
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/pyproject.toml +1 -1
- {lattice_sub-1.2.2 → lattice_sub-1.3.1/src/lattice_sub.egg-info}/PKG-INFO +15 -3
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/src/lattice_subtraction/__init__.py +1 -1
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/src/lattice_subtraction/cli.py +15 -2
- lattice_sub-1.3.1/src/lattice_subtraction/visualization.py +370 -0
- lattice_sub-1.2.2/docs/images/example_comparison.png +0 -0
- lattice_sub-1.2.2/src/lattice_subtraction/visualization.py +0 -199
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/LICENSE +0 -0
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/MANIFEST.in +0 -0
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/docs/images/threshold_analysis.png +0 -0
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/examples/converted_params.yaml +0 -0
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/setup.cfg +0 -0
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/src/lattice_sub.egg-info/SOURCES.txt +0 -0
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/src/lattice_sub.egg-info/dependency_links.txt +0 -0
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/src/lattice_sub.egg-info/entry_points.txt +0 -0
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/src/lattice_sub.egg-info/requires.txt +0 -0
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/src/lattice_sub.egg-info/top_level.txt +0 -0
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/src/lattice_subtraction/batch.py +0 -0
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/src/lattice_subtraction/config.py +0 -0
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/src/lattice_subtraction/core.py +0 -0
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/src/lattice_subtraction/io.py +0 -0
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/src/lattice_subtraction/masks.py +0 -0
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/src/lattice_subtraction/processing.py +0 -0
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/src/lattice_subtraction/threshold_optimizer.py +0 -0
- {lattice_sub-1.2.2 → lattice_sub-1.3.1}/src/lattice_subtraction/ui.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lattice-sub
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.1
|
|
4
4
|
Summary: Lattice subtraction for cryo-EM micrographs - removes periodic crystal signals to reveal non-periodic features
|
|
5
5
|
Author-email: George Stephenson <george.stephenson@colorado.edu>, Vignesh Kasinath <vignesh.kasinath@colorado.edu>
|
|
6
6
|
License: MIT
|
|
@@ -94,7 +94,17 @@ lattice-sub batch input_folder/ output_folder/ --pixel-size 0.56
|
|
|
94
94
|
lattice-sub batch input_folder/ output_folder/ --pixel-size 0.56 --vis comparisons/
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
This creates
|
|
97
|
+
This creates 4-panel PNG comparison images for each micrograph showing:
|
|
98
|
+
1. **Original** - Input micrograph
|
|
99
|
+
2. **Subtracted** - Lattice-removed result
|
|
100
|
+
3. **Difference** - What was removed (5x amplified)
|
|
101
|
+
4. **Threshold Curve** - Threshold vs lattice removal efficacy
|
|
102
|
+
|
|
103
|
+
**Limit the number of visualizations:**
|
|
104
|
+
```bash
|
|
105
|
+
# Generate visualizations for first 10 images only
|
|
106
|
+
lattice-sub batch input_folder/ output_folder/ -p 0.56 --vis comparisons/ -n 10
|
|
107
|
+
```
|
|
98
108
|
|
|
99
109
|
---
|
|
100
110
|
|
|
@@ -105,6 +115,8 @@ This creates side-by-side PNG images showing before/after/difference for each mi
|
|
|
105
115
|
| `-p, --pixel-size` | **Required.** Pixel size in Ångstroms |
|
|
106
116
|
| `-o, --output` | Output file path (default: `sub_<input>`) |
|
|
107
117
|
| `-t, --threshold` | Peak detection sensitivity (default: **auto** - optimized per image) |
|
|
118
|
+
| `--vis DIR` | Generate 4-panel comparison PNGs in DIR |
|
|
119
|
+
| `-n, --num-vis N` | Limit visualizations to first N images |
|
|
108
120
|
| `--cpu` | Force CPU processing (GPU is used by default) |
|
|
109
121
|
| `-q, --quiet` | Hide the banner and progress messages |
|
|
110
122
|
| `-v, --verbose` | Show detailed processing information |
|
|
@@ -207,7 +219,7 @@ lattice-sub batch input/ output/ -p 0.56
|
|
|
207
219
|
|
|
208
220
|
**Output:**
|
|
209
221
|
```
|
|
210
|
-
Phase-preserving FFT inpainting for cryo-EM | v1.
|
|
222
|
+
Phase-preserving FFT inpainting for cryo-EM | v1.3.0
|
|
211
223
|
|
|
212
224
|
Configuration
|
|
213
225
|
-------------
|
|
@@ -53,7 +53,17 @@ lattice-sub batch input_folder/ output_folder/ --pixel-size 0.56
|
|
|
53
53
|
lattice-sub batch input_folder/ output_folder/ --pixel-size 0.56 --vis comparisons/
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
This creates
|
|
56
|
+
This creates 4-panel PNG comparison images for each micrograph showing:
|
|
57
|
+
1. **Original** - Input micrograph
|
|
58
|
+
2. **Subtracted** - Lattice-removed result
|
|
59
|
+
3. **Difference** - What was removed (5x amplified)
|
|
60
|
+
4. **Threshold Curve** - Threshold vs lattice removal efficacy
|
|
61
|
+
|
|
62
|
+
**Limit the number of visualizations:**
|
|
63
|
+
```bash
|
|
64
|
+
# Generate visualizations for first 10 images only
|
|
65
|
+
lattice-sub batch input_folder/ output_folder/ -p 0.56 --vis comparisons/ -n 10
|
|
66
|
+
```
|
|
57
67
|
|
|
58
68
|
---
|
|
59
69
|
|
|
@@ -64,6 +74,8 @@ This creates side-by-side PNG images showing before/after/difference for each mi
|
|
|
64
74
|
| `-p, --pixel-size` | **Required.** Pixel size in Ångstroms |
|
|
65
75
|
| `-o, --output` | Output file path (default: `sub_<input>`) |
|
|
66
76
|
| `-t, --threshold` | Peak detection sensitivity (default: **auto** - optimized per image) |
|
|
77
|
+
| `--vis DIR` | Generate 4-panel comparison PNGs in DIR |
|
|
78
|
+
| `-n, --num-vis N` | Limit visualizations to first N images |
|
|
67
79
|
| `--cpu` | Force CPU processing (GPU is used by default) |
|
|
68
80
|
| `-q, --quiet` | Hide the banner and progress messages |
|
|
69
81
|
| `-v, --verbose` | Show detailed processing information |
|
|
@@ -166,7 +178,7 @@ lattice-sub batch input/ output/ -p 0.56
|
|
|
166
178
|
|
|
167
179
|
**Output:**
|
|
168
180
|
```
|
|
169
|
-
Phase-preserving FFT inpainting for cryo-EM | v1.
|
|
181
|
+
Phase-preserving FFT inpainting for cryo-EM | v1.3.0
|
|
170
182
|
|
|
171
183
|
Configuration
|
|
172
184
|
-------------
|
|
Binary file
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# Lattice Subtraction - Example Configuration
|
|
1
|
+
# Lattice Subtraction - Example Configuration
|
|
2
2
|
#
|
|
3
3
|
# This file contains all available configuration options.
|
|
4
4
|
# Copy and modify for your specific dataset.
|
|
5
5
|
#
|
|
6
|
-
#
|
|
6
|
+
# Auto-threshold and Kornia GPU acceleration are defaults.
|
|
7
7
|
# Just run `lattice-sub process image.mrc -p 0.56` for optimal results.
|
|
8
8
|
|
|
9
9
|
# ============================================
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "lattice-sub"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.3.1"
|
|
8
8
|
description = "Lattice subtraction for cryo-EM micrographs - removes periodic crystal signals to reveal non-periodic features"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lattice-sub
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.1
|
|
4
4
|
Summary: Lattice subtraction for cryo-EM micrographs - removes periodic crystal signals to reveal non-periodic features
|
|
5
5
|
Author-email: George Stephenson <george.stephenson@colorado.edu>, Vignesh Kasinath <vignesh.kasinath@colorado.edu>
|
|
6
6
|
License: MIT
|
|
@@ -94,7 +94,17 @@ lattice-sub batch input_folder/ output_folder/ --pixel-size 0.56
|
|
|
94
94
|
lattice-sub batch input_folder/ output_folder/ --pixel-size 0.56 --vis comparisons/
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
This creates
|
|
97
|
+
This creates 4-panel PNG comparison images for each micrograph showing:
|
|
98
|
+
1. **Original** - Input micrograph
|
|
99
|
+
2. **Subtracted** - Lattice-removed result
|
|
100
|
+
3. **Difference** - What was removed (5x amplified)
|
|
101
|
+
4. **Threshold Curve** - Threshold vs lattice removal efficacy
|
|
102
|
+
|
|
103
|
+
**Limit the number of visualizations:**
|
|
104
|
+
```bash
|
|
105
|
+
# Generate visualizations for first 10 images only
|
|
106
|
+
lattice-sub batch input_folder/ output_folder/ -p 0.56 --vis comparisons/ -n 10
|
|
107
|
+
```
|
|
98
108
|
|
|
99
109
|
---
|
|
100
110
|
|
|
@@ -105,6 +115,8 @@ This creates side-by-side PNG images showing before/after/difference for each mi
|
|
|
105
115
|
| `-p, --pixel-size` | **Required.** Pixel size in Ångstroms |
|
|
106
116
|
| `-o, --output` | Output file path (default: `sub_<input>`) |
|
|
107
117
|
| `-t, --threshold` | Peak detection sensitivity (default: **auto** - optimized per image) |
|
|
118
|
+
| `--vis DIR` | Generate 4-panel comparison PNGs in DIR |
|
|
119
|
+
| `-n, --num-vis N` | Limit visualizations to first N images |
|
|
108
120
|
| `--cpu` | Force CPU processing (GPU is used by default) |
|
|
109
121
|
| `-q, --quiet` | Hide the banner and progress messages |
|
|
110
122
|
| `-v, --verbose` | Show detailed processing information |
|
|
@@ -207,7 +219,7 @@ lattice-sub batch input/ output/ -p 0.56
|
|
|
207
219
|
|
|
208
220
|
**Output:**
|
|
209
221
|
```
|
|
210
|
-
Phase-preserving FFT inpainting for cryo-EM | v1.
|
|
222
|
+
Phase-preserving FFT inpainting for cryo-EM | v1.3.0
|
|
211
223
|
|
|
212
224
|
Configuration
|
|
213
225
|
-------------
|
|
@@ -153,9 +153,12 @@ def main():
|
|
|
153
153
|
# Batch process directory (GPU handles parallelism)
|
|
154
154
|
lattice-sub batch input_dir/ output_dir/ --pixel-size 0.56
|
|
155
155
|
|
|
156
|
-
# Batch with visualizations
|
|
156
|
+
# Batch with visualizations (4-panel with threshold curve)
|
|
157
157
|
lattice-sub batch input_dir/ output_dir/ -p 0.56 --vis viz_dir/
|
|
158
158
|
|
|
159
|
+
# Batch with limited visualizations (only first 10)
|
|
160
|
+
lattice-sub batch input_dir/ output_dir/ -p 0.56 --vis viz_dir/ -n 10
|
|
161
|
+
|
|
159
162
|
# CPU batch with parallel workers (use -j only with --cpu)
|
|
160
163
|
lattice-sub batch input_dir/ output_dir/ -p 0.56 --cpu -j 8
|
|
161
164
|
|
|
@@ -502,6 +505,12 @@ def process(
|
|
|
502
505
|
default=None,
|
|
503
506
|
help="Generate comparison visualizations in this directory",
|
|
504
507
|
)
|
|
508
|
+
@click.option(
|
|
509
|
+
"-n", "--num-vis",
|
|
510
|
+
type=int,
|
|
511
|
+
default=None,
|
|
512
|
+
help="Number of visualizations to generate (default: all)",
|
|
513
|
+
)
|
|
505
514
|
@click.option(
|
|
506
515
|
"-v", "--verbose",
|
|
507
516
|
is_flag=True,
|
|
@@ -528,6 +537,7 @@ def batch(
|
|
|
528
537
|
config: Optional[str],
|
|
529
538
|
recursive: bool,
|
|
530
539
|
vis: Optional[str],
|
|
540
|
+
num_vis: Optional[int],
|
|
531
541
|
verbose: bool,
|
|
532
542
|
quiet: bool,
|
|
533
543
|
cpu: bool,
|
|
@@ -617,7 +627,8 @@ def batch(
|
|
|
617
627
|
|
|
618
628
|
# Generate visualizations if requested
|
|
619
629
|
if vis:
|
|
620
|
-
|
|
630
|
+
limit_msg = f" (limit: {num_vis})" if num_vis else ""
|
|
631
|
+
logger.info(f"Generating visualizations in: {vis}{limit_msg}")
|
|
621
632
|
viz_success, viz_total = generate_visualizations(
|
|
622
633
|
input_dir=input_dir,
|
|
623
634
|
output_dir=output_dir,
|
|
@@ -625,6 +636,8 @@ def batch(
|
|
|
625
636
|
prefix=prefix,
|
|
626
637
|
pattern=pattern,
|
|
627
638
|
show_progress=True,
|
|
639
|
+
limit=num_vis,
|
|
640
|
+
config=cfg,
|
|
628
641
|
)
|
|
629
642
|
logger.info(f"Visualizations: {viz_success}/{viz_total} created")
|
|
630
643
|
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Visualization utilities for lattice subtraction results.
|
|
3
|
+
|
|
4
|
+
This module provides functions to create comparison visualizations
|
|
5
|
+
showing original, processed, difference images, and threshold optimization curves.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional, Tuple, List
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
# Silence matplotlib's verbose debug logging
|
|
15
|
+
logging.getLogger('matplotlib').setLevel(logging.WARNING)
|
|
16
|
+
logging.getLogger('matplotlib.font_manager').setLevel(logging.WARNING)
|
|
17
|
+
|
|
18
|
+
# Silence PIL/Pillow debug logging (PNG chunk messages)
|
|
19
|
+
logging.getLogger('PIL').setLevel(logging.WARNING)
|
|
20
|
+
logging.getLogger('PIL.PngImagePlugin').setLevel(logging.WARNING)
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def compute_threshold_curve(
|
|
26
|
+
image: np.ndarray,
|
|
27
|
+
config,
|
|
28
|
+
n_points: int = 21,
|
|
29
|
+
) -> Tuple[np.ndarray, np.ndarray, float, float]:
|
|
30
|
+
"""
|
|
31
|
+
Compute quality scores across a range of thresholds.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
image: Original image array
|
|
35
|
+
config: Config object with processing parameters
|
|
36
|
+
n_points: Number of threshold points to evaluate
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Tuple of (thresholds, quality_scores, optimal_threshold, optimal_quality)
|
|
40
|
+
"""
|
|
41
|
+
from .threshold_optimizer import ThresholdOptimizer
|
|
42
|
+
|
|
43
|
+
optimizer = ThresholdOptimizer(config)
|
|
44
|
+
|
|
45
|
+
# Prepare FFT data once
|
|
46
|
+
subtracted, radial_mask, box_size = optimizer._prepare_fft_data(image)
|
|
47
|
+
|
|
48
|
+
# Evaluate across threshold range
|
|
49
|
+
thresholds = np.linspace(
|
|
50
|
+
optimizer.min_threshold,
|
|
51
|
+
optimizer.max_threshold,
|
|
52
|
+
n_points
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Use GPU batch if available
|
|
56
|
+
if optimizer.use_gpu:
|
|
57
|
+
qualities, peak_counts = optimizer._compute_quality_batch_gpu(
|
|
58
|
+
subtracted, radial_mask, thresholds
|
|
59
|
+
)
|
|
60
|
+
else:
|
|
61
|
+
qualities = []
|
|
62
|
+
for t in thresholds:
|
|
63
|
+
q, _ = optimizer._compute_quality(subtracted, radial_mask, t)
|
|
64
|
+
qualities.append(q)
|
|
65
|
+
qualities = np.array(qualities)
|
|
66
|
+
|
|
67
|
+
# Find optimal
|
|
68
|
+
best_idx = np.argmax(qualities)
|
|
69
|
+
optimal_threshold = thresholds[best_idx]
|
|
70
|
+
optimal_quality = qualities[best_idx]
|
|
71
|
+
|
|
72
|
+
return thresholds, qualities, optimal_threshold, optimal_quality
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def create_comparison_figure(
|
|
76
|
+
original: np.ndarray,
|
|
77
|
+
processed: np.ndarray,
|
|
78
|
+
title: str = "Lattice Subtraction Comparison",
|
|
79
|
+
figsize: Tuple[int, int] = (18, 6),
|
|
80
|
+
dpi: int = 150,
|
|
81
|
+
):
|
|
82
|
+
"""
|
|
83
|
+
Create a comparison figure showing original, processed, and difference images.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
original: Original image array
|
|
87
|
+
processed: Processed (lattice-subtracted) image array
|
|
88
|
+
title: Figure title
|
|
89
|
+
figsize: Figure size in inches (width, height)
|
|
90
|
+
dpi: Resolution for saving
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
matplotlib Figure object
|
|
94
|
+
"""
|
|
95
|
+
import matplotlib.pyplot as plt
|
|
96
|
+
|
|
97
|
+
# Compute difference
|
|
98
|
+
difference = original - processed
|
|
99
|
+
|
|
100
|
+
# Create figure
|
|
101
|
+
fig, axes = plt.subplots(1, 3, figsize=figsize)
|
|
102
|
+
|
|
103
|
+
# Contrast limits from original
|
|
104
|
+
vmin, vmax = np.percentile(original, [1, 99])
|
|
105
|
+
|
|
106
|
+
# Original
|
|
107
|
+
axes[0].imshow(original, cmap='gray', vmin=vmin, vmax=vmax)
|
|
108
|
+
axes[0].set_title(f'Original\n{original.shape}')
|
|
109
|
+
axes[0].axis('off')
|
|
110
|
+
|
|
111
|
+
# Lattice Subtracted
|
|
112
|
+
axes[1].imshow(processed, cmap='gray', vmin=vmin, vmax=vmax)
|
|
113
|
+
axes[1].set_title(f'Lattice Subtracted\n{processed.shape}')
|
|
114
|
+
axes[1].axis('off')
|
|
115
|
+
|
|
116
|
+
# Difference (removed lattice)
|
|
117
|
+
diff_std = np.std(difference)
|
|
118
|
+
axes[2].imshow(
|
|
119
|
+
difference,
|
|
120
|
+
cmap='RdBu_r',
|
|
121
|
+
vmin=-diff_std * 3,
|
|
122
|
+
vmax=diff_std * 3
|
|
123
|
+
)
|
|
124
|
+
axes[2].set_title('Difference (Removed Lattice)')
|
|
125
|
+
axes[2].axis('off')
|
|
126
|
+
|
|
127
|
+
# Title
|
|
128
|
+
plt.suptitle(title, fontsize=14)
|
|
129
|
+
plt.tight_layout()
|
|
130
|
+
|
|
131
|
+
return fig
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def create_comparison_figure_with_threshold(
|
|
135
|
+
original: np.ndarray,
|
|
136
|
+
processed: np.ndarray,
|
|
137
|
+
thresholds: np.ndarray,
|
|
138
|
+
quality_scores: np.ndarray,
|
|
139
|
+
optimal_threshold: float,
|
|
140
|
+
optimal_quality: float,
|
|
141
|
+
title: str = "Lattice Subtraction Comparison",
|
|
142
|
+
figsize: Tuple[int, int] = (14, 12),
|
|
143
|
+
dpi: int = 150,
|
|
144
|
+
):
|
|
145
|
+
"""
|
|
146
|
+
Create a 4-panel comparison figure with threshold optimization curve.
|
|
147
|
+
|
|
148
|
+
Layout (2x2 grid):
|
|
149
|
+
[Original] [Subtracted]
|
|
150
|
+
[Difference] [Threshold Curve]
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
original: Original image array
|
|
154
|
+
processed: Processed (lattice-subtracted) image array
|
|
155
|
+
thresholds: Array of threshold values tested
|
|
156
|
+
quality_scores: Array of quality scores for each threshold
|
|
157
|
+
optimal_threshold: The optimal threshold that was selected
|
|
158
|
+
optimal_quality: Quality score at optimal threshold
|
|
159
|
+
title: Figure title
|
|
160
|
+
figsize: Figure size in inches (width, height)
|
|
161
|
+
dpi: Resolution for saving
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
matplotlib Figure object
|
|
165
|
+
"""
|
|
166
|
+
import matplotlib.pyplot as plt
|
|
167
|
+
|
|
168
|
+
# Compute difference
|
|
169
|
+
difference = original - processed
|
|
170
|
+
|
|
171
|
+
# Create figure with 4 panels (2 rows, 2 columns)
|
|
172
|
+
fig, axes = plt.subplots(2, 2, figsize=figsize)
|
|
173
|
+
|
|
174
|
+
# Contrast limits from original
|
|
175
|
+
vmin, vmax = np.percentile(original, [1, 99])
|
|
176
|
+
|
|
177
|
+
# Panel 1 (top-left): Original
|
|
178
|
+
axes[0, 0].imshow(original, cmap='gray', vmin=vmin, vmax=vmax)
|
|
179
|
+
axes[0, 0].set_title(f'Original\n{original.shape}')
|
|
180
|
+
axes[0, 0].axis('off')
|
|
181
|
+
|
|
182
|
+
# Panel 2 (top-right): Lattice Subtracted
|
|
183
|
+
axes[0, 1].imshow(processed, cmap='gray', vmin=vmin, vmax=vmax)
|
|
184
|
+
axes[0, 1].set_title(f'Lattice Subtracted\n{processed.shape}')
|
|
185
|
+
axes[0, 1].axis('off')
|
|
186
|
+
|
|
187
|
+
# Panel 3 (bottom-left): Difference (removed lattice)
|
|
188
|
+
diff_std = np.std(difference)
|
|
189
|
+
axes[1, 0].imshow(
|
|
190
|
+
difference,
|
|
191
|
+
cmap='RdBu_r',
|
|
192
|
+
vmin=-diff_std * 3,
|
|
193
|
+
vmax=diff_std * 3
|
|
194
|
+
)
|
|
195
|
+
axes[1, 0].set_title('Difference (Removed Lattice)')
|
|
196
|
+
axes[1, 0].axis('off')
|
|
197
|
+
|
|
198
|
+
# Panel 4 (bottom-right): Threshold vs Quality Score curve
|
|
199
|
+
axes[1, 1].plot(thresholds, quality_scores, 'b-', linewidth=2, label='Quality Score')
|
|
200
|
+
axes[1, 1].axvline(x=optimal_threshold, color='r', linestyle='--', linewidth=2,
|
|
201
|
+
label=f'Optimal: {optimal_threshold:.3f}')
|
|
202
|
+
axes[1, 1].scatter([optimal_threshold], [optimal_quality], color='r', s=100, zorder=5)
|
|
203
|
+
axes[1, 1].set_xlabel('Threshold', fontsize=11)
|
|
204
|
+
axes[1, 1].set_ylabel('Lattice Removal Efficacy', fontsize=11)
|
|
205
|
+
axes[1, 1].set_title(f'Threshold Optimization\nOptimal = {optimal_threshold:.3f}')
|
|
206
|
+
axes[1, 1].legend(loc='best', fontsize=9)
|
|
207
|
+
axes[1, 1].grid(True, alpha=0.3)
|
|
208
|
+
axes[1, 1].set_xlim(thresholds.min(), thresholds.max())
|
|
209
|
+
|
|
210
|
+
# Title
|
|
211
|
+
plt.suptitle(title, fontsize=14)
|
|
212
|
+
plt.tight_layout()
|
|
213
|
+
|
|
214
|
+
return fig
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def save_comparison_visualization(
|
|
218
|
+
original_path: Path,
|
|
219
|
+
processed_path: Path,
|
|
220
|
+
output_path: Path,
|
|
221
|
+
config = None,
|
|
222
|
+
dpi: int = 150,
|
|
223
|
+
) -> None:
|
|
224
|
+
"""
|
|
225
|
+
Create and save a 4-panel comparison visualization for a single image pair.
|
|
226
|
+
|
|
227
|
+
Includes threshold optimization curve showing how the optimal threshold
|
|
228
|
+
was selected based on lattice removal efficacy.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
original_path: Path to original MRC file
|
|
232
|
+
processed_path: Path to processed MRC file
|
|
233
|
+
output_path: Path for output PNG file
|
|
234
|
+
config: Config object for threshold computation (optional, will use defaults)
|
|
235
|
+
dpi: Resolution for output images
|
|
236
|
+
"""
|
|
237
|
+
import matplotlib.pyplot as plt
|
|
238
|
+
import mrcfile
|
|
239
|
+
|
|
240
|
+
# Load images
|
|
241
|
+
with mrcfile.open(original_path, 'r') as f:
|
|
242
|
+
original = f.data.copy()
|
|
243
|
+
with mrcfile.open(processed_path, 'r') as f:
|
|
244
|
+
processed = f.data.copy()
|
|
245
|
+
|
|
246
|
+
# Create title
|
|
247
|
+
name = original_path.name
|
|
248
|
+
short_name = name[:50] + "..." if len(name) > 50 else name
|
|
249
|
+
title = f"Lattice Subtraction: {short_name}"
|
|
250
|
+
|
|
251
|
+
# Try to compute threshold curve if config available
|
|
252
|
+
try:
|
|
253
|
+
if config is None:
|
|
254
|
+
# Create default config for threshold computation
|
|
255
|
+
from .config import Config
|
|
256
|
+
config = Config(pixel_ang=0.56) # Default K3 pixel size
|
|
257
|
+
|
|
258
|
+
# Compute threshold optimization curve
|
|
259
|
+
thresholds, quality_scores, optimal_threshold, optimal_quality = \
|
|
260
|
+
compute_threshold_curve(original, config)
|
|
261
|
+
|
|
262
|
+
# Create 4-panel figure with threshold curve
|
|
263
|
+
fig = create_comparison_figure_with_threshold(
|
|
264
|
+
original, processed,
|
|
265
|
+
thresholds, quality_scores,
|
|
266
|
+
optimal_threshold, optimal_quality,
|
|
267
|
+
title=title, dpi=dpi
|
|
268
|
+
)
|
|
269
|
+
except Exception as e:
|
|
270
|
+
# Fallback to 3-panel if threshold computation fails
|
|
271
|
+
logger.debug(f"Could not compute threshold curve: {e}, using 3-panel view")
|
|
272
|
+
fig = create_comparison_figure(original, processed, title=title, dpi=dpi)
|
|
273
|
+
|
|
274
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
275
|
+
fig.savefig(output_path, dpi=dpi, bbox_inches='tight')
|
|
276
|
+
plt.close(fig)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def generate_visualizations(
|
|
280
|
+
input_dir: Path,
|
|
281
|
+
output_dir: Path,
|
|
282
|
+
viz_dir: Path,
|
|
283
|
+
prefix: str = "sub_",
|
|
284
|
+
pattern: str = "*.mrc",
|
|
285
|
+
dpi: int = 150,
|
|
286
|
+
show_progress: bool = True,
|
|
287
|
+
limit: Optional[int] = None,
|
|
288
|
+
config = None,
|
|
289
|
+
) -> Tuple[int, int]:
|
|
290
|
+
"""
|
|
291
|
+
Generate comparison visualizations for processed images in a directory.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
input_dir: Directory containing original MRC files
|
|
295
|
+
output_dir: Directory containing processed MRC files
|
|
296
|
+
viz_dir: Directory for output visualization PNG files
|
|
297
|
+
prefix: Prefix used for processed files (default: "sub_")
|
|
298
|
+
pattern: Glob pattern for finding processed files
|
|
299
|
+
dpi: Resolution for output images
|
|
300
|
+
show_progress: Show progress bar
|
|
301
|
+
limit: Maximum number of visualizations to generate (None = all)
|
|
302
|
+
config: Config object for threshold computation (optional)
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
Tuple of (successful_count, total_count)
|
|
306
|
+
"""
|
|
307
|
+
import matplotlib.pyplot as plt
|
|
308
|
+
|
|
309
|
+
viz_dir = Path(viz_dir)
|
|
310
|
+
viz_dir.mkdir(parents=True, exist_ok=True)
|
|
311
|
+
|
|
312
|
+
# Find all processed files
|
|
313
|
+
output_files = sorted(Path(output_dir).glob(f"{prefix}{pattern}"))
|
|
314
|
+
|
|
315
|
+
if not output_files:
|
|
316
|
+
logger.warning(f"No processed files found matching '{prefix}{pattern}' in {output_dir}")
|
|
317
|
+
return 0, 0
|
|
318
|
+
|
|
319
|
+
# Apply limit if specified
|
|
320
|
+
total_available = len(output_files)
|
|
321
|
+
if limit is not None and limit > 0:
|
|
322
|
+
output_files = output_files[:limit]
|
|
323
|
+
logger.info(f"Limiting to {limit} visualizations (of {total_available} available)")
|
|
324
|
+
|
|
325
|
+
successful = 0
|
|
326
|
+
total = len(output_files)
|
|
327
|
+
|
|
328
|
+
# Setup iterator with optional progress bar
|
|
329
|
+
if show_progress:
|
|
330
|
+
try:
|
|
331
|
+
from tqdm import tqdm
|
|
332
|
+
iterator = tqdm(output_files, desc="Generating visualizations", unit="file")
|
|
333
|
+
except ImportError:
|
|
334
|
+
iterator = output_files
|
|
335
|
+
else:
|
|
336
|
+
iterator = output_files
|
|
337
|
+
|
|
338
|
+
for processed_path in iterator:
|
|
339
|
+
try:
|
|
340
|
+
# Get corresponding input file
|
|
341
|
+
input_name = processed_path.name.replace(prefix, "", 1)
|
|
342
|
+
input_path = Path(input_dir) / input_name
|
|
343
|
+
|
|
344
|
+
if not input_path.exists():
|
|
345
|
+
logger.debug(f"Original not found: {input_path}")
|
|
346
|
+
continue
|
|
347
|
+
|
|
348
|
+
# Output path
|
|
349
|
+
viz_name = input_name.replace(".mrc", ".png")
|
|
350
|
+
viz_path = viz_dir / viz_name
|
|
351
|
+
|
|
352
|
+
# Skip if already exists
|
|
353
|
+
if viz_path.exists():
|
|
354
|
+
successful += 1
|
|
355
|
+
continue
|
|
356
|
+
|
|
357
|
+
# Generate visualization with 4-panel layout
|
|
358
|
+
save_comparison_visualization(
|
|
359
|
+
original_path=input_path,
|
|
360
|
+
processed_path=processed_path,
|
|
361
|
+
output_path=viz_path,
|
|
362
|
+
config=config,
|
|
363
|
+
dpi=dpi,
|
|
364
|
+
)
|
|
365
|
+
successful += 1
|
|
366
|
+
|
|
367
|
+
except Exception as e:
|
|
368
|
+
logger.error(f"Failed to create visualization for {processed_path.name}: {e}")
|
|
369
|
+
|
|
370
|
+
return successful, total
|
|
Binary file
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Visualization utilities for lattice subtraction results.
|
|
3
|
-
|
|
4
|
-
This module provides functions to create comparison visualizations
|
|
5
|
-
showing original, processed, and difference images.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import logging
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
from typing import Optional, Tuple
|
|
11
|
-
|
|
12
|
-
import numpy as np
|
|
13
|
-
|
|
14
|
-
# Silence matplotlib's verbose debug logging
|
|
15
|
-
logging.getLogger('matplotlib').setLevel(logging.WARNING)
|
|
16
|
-
logging.getLogger('matplotlib.font_manager').setLevel(logging.WARNING)
|
|
17
|
-
|
|
18
|
-
# Silence PIL/Pillow debug logging (PNG chunk messages)
|
|
19
|
-
logging.getLogger('PIL').setLevel(logging.WARNING)
|
|
20
|
-
logging.getLogger('PIL.PngImagePlugin').setLevel(logging.WARNING)
|
|
21
|
-
|
|
22
|
-
logger = logging.getLogger(__name__)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def create_comparison_figure(
|
|
26
|
-
original: np.ndarray,
|
|
27
|
-
processed: np.ndarray,
|
|
28
|
-
title: str = "Lattice Subtraction Comparison",
|
|
29
|
-
figsize: Tuple[int, int] = (18, 6),
|
|
30
|
-
dpi: int = 150,
|
|
31
|
-
):
|
|
32
|
-
"""
|
|
33
|
-
Create a comparison figure showing original, processed, and difference images.
|
|
34
|
-
|
|
35
|
-
Args:
|
|
36
|
-
original: Original image array
|
|
37
|
-
processed: Processed (lattice-subtracted) image array
|
|
38
|
-
title: Figure title
|
|
39
|
-
figsize: Figure size in inches (width, height)
|
|
40
|
-
dpi: Resolution for saving
|
|
41
|
-
|
|
42
|
-
Returns:
|
|
43
|
-
matplotlib Figure object
|
|
44
|
-
"""
|
|
45
|
-
import matplotlib.pyplot as plt
|
|
46
|
-
|
|
47
|
-
# Compute difference
|
|
48
|
-
difference = original - processed
|
|
49
|
-
|
|
50
|
-
# Create figure
|
|
51
|
-
fig, axes = plt.subplots(1, 3, figsize=figsize)
|
|
52
|
-
|
|
53
|
-
# Contrast limits from original
|
|
54
|
-
vmin, vmax = np.percentile(original, [1, 99])
|
|
55
|
-
|
|
56
|
-
# Original
|
|
57
|
-
axes[0].imshow(original, cmap='gray', vmin=vmin, vmax=vmax)
|
|
58
|
-
axes[0].set_title(f'Original\n{original.shape}')
|
|
59
|
-
axes[0].axis('off')
|
|
60
|
-
|
|
61
|
-
# Lattice Subtracted
|
|
62
|
-
axes[1].imshow(processed, cmap='gray', vmin=vmin, vmax=vmax)
|
|
63
|
-
axes[1].set_title(f'Lattice Subtracted\n{processed.shape}')
|
|
64
|
-
axes[1].axis('off')
|
|
65
|
-
|
|
66
|
-
# Difference (removed lattice)
|
|
67
|
-
diff_std = np.std(difference)
|
|
68
|
-
axes[2].imshow(
|
|
69
|
-
difference,
|
|
70
|
-
cmap='RdBu_r',
|
|
71
|
-
vmin=-diff_std * 3,
|
|
72
|
-
vmax=diff_std * 3
|
|
73
|
-
)
|
|
74
|
-
axes[2].set_title('Difference (Removed Lattice)')
|
|
75
|
-
axes[2].axis('off')
|
|
76
|
-
|
|
77
|
-
# Title
|
|
78
|
-
plt.suptitle(title, fontsize=14)
|
|
79
|
-
plt.tight_layout()
|
|
80
|
-
|
|
81
|
-
return fig
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def save_comparison_visualization(
|
|
85
|
-
original_path: Path,
|
|
86
|
-
processed_path: Path,
|
|
87
|
-
output_path: Path,
|
|
88
|
-
dpi: int = 150,
|
|
89
|
-
) -> None:
|
|
90
|
-
"""
|
|
91
|
-
Create and save a comparison visualization for a single image pair.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
original_path: Path to original MRC file
|
|
95
|
-
processed_path: Path to processed MRC file
|
|
96
|
-
output_path: Path for output PNG file
|
|
97
|
-
"""
|
|
98
|
-
import matplotlib.pyplot as plt
|
|
99
|
-
import mrcfile
|
|
100
|
-
|
|
101
|
-
# Load images
|
|
102
|
-
with mrcfile.open(original_path, 'r') as f:
|
|
103
|
-
original = f.data.copy()
|
|
104
|
-
with mrcfile.open(processed_path, 'r') as f:
|
|
105
|
-
processed = f.data.copy()
|
|
106
|
-
|
|
107
|
-
# Create title
|
|
108
|
-
name = original_path.name
|
|
109
|
-
short_name = name[:60] + "..." if len(name) > 60 else name
|
|
110
|
-
title = f"Lattice Subtraction Comparison: {short_name}"
|
|
111
|
-
|
|
112
|
-
# Create and save figure
|
|
113
|
-
fig = create_comparison_figure(original, processed, title=title, dpi=dpi)
|
|
114
|
-
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
115
|
-
fig.savefig(output_path, dpi=dpi, bbox_inches='tight')
|
|
116
|
-
plt.close(fig)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def generate_visualizations(
|
|
120
|
-
input_dir: Path,
|
|
121
|
-
output_dir: Path,
|
|
122
|
-
viz_dir: Path,
|
|
123
|
-
prefix: str = "sub_",
|
|
124
|
-
pattern: str = "*.mrc",
|
|
125
|
-
dpi: int = 150,
|
|
126
|
-
show_progress: bool = True,
|
|
127
|
-
) -> Tuple[int, int]:
|
|
128
|
-
"""
|
|
129
|
-
Generate comparison visualizations for all processed images in a directory.
|
|
130
|
-
|
|
131
|
-
Args:
|
|
132
|
-
input_dir: Directory containing original MRC files
|
|
133
|
-
output_dir: Directory containing processed MRC files
|
|
134
|
-
viz_dir: Directory for output visualization PNG files
|
|
135
|
-
prefix: Prefix used for processed files (default: "sub_")
|
|
136
|
-
pattern: Glob pattern for finding processed files
|
|
137
|
-
dpi: Resolution for output images
|
|
138
|
-
show_progress: Show progress bar
|
|
139
|
-
|
|
140
|
-
Returns:
|
|
141
|
-
Tuple of (successful_count, total_count)
|
|
142
|
-
"""
|
|
143
|
-
import matplotlib.pyplot as plt
|
|
144
|
-
|
|
145
|
-
viz_dir = Path(viz_dir)
|
|
146
|
-
viz_dir.mkdir(parents=True, exist_ok=True)
|
|
147
|
-
|
|
148
|
-
# Find all processed files
|
|
149
|
-
output_files = sorted(Path(output_dir).glob(f"{prefix}{pattern}"))
|
|
150
|
-
|
|
151
|
-
if not output_files:
|
|
152
|
-
logger.warning(f"No processed files found matching '{prefix}{pattern}' in {output_dir}")
|
|
153
|
-
return 0, 0
|
|
154
|
-
|
|
155
|
-
successful = 0
|
|
156
|
-
total = len(output_files)
|
|
157
|
-
|
|
158
|
-
# Setup iterator with optional progress bar
|
|
159
|
-
if show_progress:
|
|
160
|
-
try:
|
|
161
|
-
from tqdm import tqdm
|
|
162
|
-
iterator = tqdm(output_files, desc="Generating visualizations", unit="file")
|
|
163
|
-
except ImportError:
|
|
164
|
-
iterator = output_files
|
|
165
|
-
else:
|
|
166
|
-
iterator = output_files
|
|
167
|
-
|
|
168
|
-
for processed_path in iterator:
|
|
169
|
-
try:
|
|
170
|
-
# Get corresponding input file
|
|
171
|
-
input_name = processed_path.name.replace(prefix, "", 1)
|
|
172
|
-
input_path = Path(input_dir) / input_name
|
|
173
|
-
|
|
174
|
-
if not input_path.exists():
|
|
175
|
-
logger.debug(f"Original not found: {input_path}")
|
|
176
|
-
continue
|
|
177
|
-
|
|
178
|
-
# Output path
|
|
179
|
-
viz_name = input_name.replace(".mrc", ".png")
|
|
180
|
-
viz_path = viz_dir / viz_name
|
|
181
|
-
|
|
182
|
-
# Skip if already exists
|
|
183
|
-
if viz_path.exists():
|
|
184
|
-
successful += 1
|
|
185
|
-
continue
|
|
186
|
-
|
|
187
|
-
# Generate visualization
|
|
188
|
-
save_comparison_visualization(
|
|
189
|
-
original_path=input_path,
|
|
190
|
-
processed_path=processed_path,
|
|
191
|
-
output_path=viz_path,
|
|
192
|
-
dpi=dpi,
|
|
193
|
-
)
|
|
194
|
-
successful += 1
|
|
195
|
-
|
|
196
|
-
except Exception as e:
|
|
197
|
-
logger.error(f"Failed to create visualization for {processed_path.name}: {e}")
|
|
198
|
-
|
|
199
|
-
return successful, total
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|