lattice-sub 1.2.2__py3-none-any.whl → 1.3.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lattice-sub
3
- Version: 1.2.2
3
+ Version: 1.3.0
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 side-by-side PNG images showing before/after/difference for each micrograph.
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.2.2
222
+ Phase-preserving FFT inpainting for cryo-EM | v1.3.0
211
223
 
212
224
  Configuration
213
225
  -------------
@@ -1,7 +1,7 @@
1
- lattice_sub-1.2.2.dist-info/licenses/LICENSE,sha256=2kPoH0cbEp0cVEGqMpyF2IQX1npxdtQmWJB__HIRSb0,1101
2
- lattice_subtraction/__init__.py,sha256=UkIWBvAFMQ3YgK1t8DwHaGqufsArImuHhBAp5iCBC2E,1737
1
+ lattice_sub-1.3.0.dist-info/licenses/LICENSE,sha256=2kPoH0cbEp0cVEGqMpyF2IQX1npxdtQmWJB__HIRSb0,1101
2
+ lattice_subtraction/__init__.py,sha256=TNaJXvSgCQdvYUYJfS5scn92YjORiGfLot9WadZ8u28,1737
3
3
  lattice_subtraction/batch.py,sha256=zJzvUnr8dznvxE8jaPKDLJ7AcJg8Cbfv5nVo0FzZz1I,20891
4
- lattice_subtraction/cli.py,sha256=fWZ3ueahQXPQ_ApzS345AdslsMF0-6HIAIvan2IFHkk,23597
4
+ lattice_subtraction/cli.py,sha256=W99XQClUMKaaFQxle0W-ILQ6UuYRFXZVJWD4qXpcIj4,24063
5
5
  lattice_subtraction/config.py,sha256=uzwKb5Zi3phHUk2ZgoiLsQdwFdN-rTiY8n02U91SObc,8426
6
6
  lattice_subtraction/core.py,sha256=VzcecSZHRuBuHUc2jHGv8LalINL75RH0aTpABI708y8,16265
7
7
  lattice_subtraction/io.py,sha256=uHku6rJ0jeCph7w-gOIDJx-xpNoF6PZcLfb5TBTOiw0,4594
@@ -9,9 +9,9 @@ lattice_subtraction/masks.py,sha256=HIamrACmbQDkaCV4kXhnjMDSwIig4OtQFLig9A8PMO8,
9
9
  lattice_subtraction/processing.py,sha256=tmnj5K4Z9HCQhRpJ-iMd9Bj_uTRuvDEWyUenh8MCWEM,8341
10
10
  lattice_subtraction/threshold_optimizer.py,sha256=yEsGM_zt6YjgEulEZqtRy113xOFB69aHJIETm2xSS6k,15398
11
11
  lattice_subtraction/ui.py,sha256=Sp_a-yNmBRZJxll8h9T_H5-_KsI13zGYmHcbcpVpbR8,9176
12
- lattice_subtraction/visualization.py,sha256=pMZKcz6Xgs98lLaZbvGjoMIyEYA_MLRracVxpQStC3w,5935
13
- lattice_sub-1.2.2.dist-info/METADATA,sha256=u8Fy9n7k3bXazhftD4Zl4_yPgY3Aki9Jv3Sm4EjzBUk,14419
14
- lattice_sub-1.2.2.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
15
- lattice_sub-1.2.2.dist-info/entry_points.txt,sha256=o8PzJR8kFnXlKZufoYGBIHpiosM-P4PZeKZXJjtPS6Y,61
16
- lattice_sub-1.2.2.dist-info/top_level.txt,sha256=BOuW-sm4G-fQtsWPRdeLzWn0WS8sDYVNKIMj5I3JXew,20
17
- lattice_sub-1.2.2.dist-info/RECORD,,
12
+ lattice_subtraction/visualization.py,sha256=hWFz49NBBrS7d6ofO0VyJ6-v8Q6hPG1dijbDtecMOQs,11890
13
+ lattice_sub-1.3.0.dist-info/METADATA,sha256=pKwt8TcftbZGm1gvWZGO1n3iQiI4JB3E_ix3InB-4D0,14901
14
+ lattice_sub-1.3.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
15
+ lattice_sub-1.3.0.dist-info/entry_points.txt,sha256=o8PzJR8kFnXlKZufoYGBIHpiosM-P4PZeKZXJjtPS6Y,61
16
+ lattice_sub-1.3.0.dist-info/top_level.txt,sha256=BOuW-sm4G-fQtsWPRdeLzWn0WS8sDYVNKIMj5I3JXew,20
17
+ lattice_sub-1.3.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.10.1)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -19,7 +19,7 @@ Example:
19
19
  >>> result.save("output.mrc")
20
20
  """
21
21
 
22
- __version__ = "1.2.2"
22
+ __version__ = "1.3.0"
23
23
  __author__ = "George Stephenson & Vignesh Kasinath"
24
24
 
25
25
  from .config import Config
@@ -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
- logger.info(f"Generating visualizations in: {vis}")
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
 
@@ -2,12 +2,12 @@
2
2
  Visualization utilities for lattice subtraction results.
3
3
 
4
4
  This module provides functions to create comparison visualizations
5
- showing original, processed, and difference images.
5
+ showing original, processed, difference images, and threshold optimization curves.
6
6
  """
7
7
 
8
8
  import logging
9
9
  from pathlib import Path
10
- from typing import Optional, Tuple
10
+ from typing import Optional, Tuple, List
11
11
 
12
12
  import numpy as np
13
13
 
@@ -22,6 +22,56 @@ logging.getLogger('PIL.PngImagePlugin').setLevel(logging.WARNING)
22
22
  logger = logging.getLogger(__name__)
23
23
 
24
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
+
25
75
  def create_comparison_figure(
26
76
  original: np.ndarray,
27
77
  processed: np.ndarray,
@@ -81,19 +131,106 @@ def create_comparison_figure(
81
131
  return fig
82
132
 
83
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] = (24, 6),
143
+ dpi: int = 150,
144
+ ):
145
+ """
146
+ Create a 4-panel comparison figure with threshold optimization curve.
147
+
148
+ Layout: [Original] [Subtracted] [Difference] [Threshold vs Quality]
149
+
150
+ Args:
151
+ original: Original image array
152
+ processed: Processed (lattice-subtracted) image array
153
+ thresholds: Array of threshold values tested
154
+ quality_scores: Array of quality scores for each threshold
155
+ optimal_threshold: The optimal threshold that was selected
156
+ optimal_quality: Quality score at optimal threshold
157
+ title: Figure title
158
+ figsize: Figure size in inches (width, height)
159
+ dpi: Resolution for saving
160
+
161
+ Returns:
162
+ matplotlib Figure object
163
+ """
164
+ import matplotlib.pyplot as plt
165
+
166
+ # Compute difference
167
+ difference = original - processed
168
+
169
+ # Create figure with 4 panels (1 row, 4 columns)
170
+ fig, axes = plt.subplots(1, 4, figsize=figsize)
171
+
172
+ # Contrast limits from original
173
+ vmin, vmax = np.percentile(original, [1, 99])
174
+
175
+ # Panel 1: Original
176
+ axes[0].imshow(original, cmap='gray', vmin=vmin, vmax=vmax)
177
+ axes[0].set_title(f'Original\n{original.shape}')
178
+ axes[0].axis('off')
179
+
180
+ # Panel 2: Lattice Subtracted
181
+ axes[1].imshow(processed, cmap='gray', vmin=vmin, vmax=vmax)
182
+ axes[1].set_title(f'Lattice Subtracted\n{processed.shape}')
183
+ axes[1].axis('off')
184
+
185
+ # Panel 3: Difference (removed lattice)
186
+ diff_std = np.std(difference)
187
+ axes[2].imshow(
188
+ difference,
189
+ cmap='RdBu_r',
190
+ vmin=-diff_std * 3,
191
+ vmax=diff_std * 3
192
+ )
193
+ axes[2].set_title('Difference (Removed Lattice)')
194
+ axes[2].axis('off')
195
+
196
+ # Panel 4: Threshold vs Quality Score curve
197
+ axes[3].plot(thresholds, quality_scores, 'b-', linewidth=2, label='Quality Score')
198
+ axes[3].axvline(x=optimal_threshold, color='r', linestyle='--', linewidth=2,
199
+ label=f'Optimal: {optimal_threshold:.3f}')
200
+ axes[3].scatter([optimal_threshold], [optimal_quality], color='r', s=100, zorder=5)
201
+ axes[3].set_xlabel('Threshold', fontsize=11)
202
+ axes[3].set_ylabel('Lattice Removal Efficacy', fontsize=11)
203
+ axes[3].set_title(f'Threshold Optimization\nOptimal = {optimal_threshold:.3f}')
204
+ axes[3].legend(loc='best', fontsize=9)
205
+ axes[3].grid(True, alpha=0.3)
206
+ axes[3].set_xlim(thresholds.min(), thresholds.max())
207
+
208
+ # Title
209
+ plt.suptitle(title, fontsize=14)
210
+ plt.tight_layout()
211
+
212
+ return fig
213
+
214
+
84
215
  def save_comparison_visualization(
85
216
  original_path: Path,
86
217
  processed_path: Path,
87
218
  output_path: Path,
219
+ config = None,
88
220
  dpi: int = 150,
89
221
  ) -> None:
90
222
  """
91
- Create and save a comparison visualization for a single image pair.
223
+ Create and save a 4-panel comparison visualization for a single image pair.
224
+
225
+ Includes threshold optimization curve showing how the optimal threshold
226
+ was selected based on lattice removal efficacy.
92
227
 
93
228
  Args:
94
229
  original_path: Path to original MRC file
95
230
  processed_path: Path to processed MRC file
96
231
  output_path: Path for output PNG file
232
+ config: Config object for threshold computation (optional, will use defaults)
233
+ dpi: Resolution for output images
97
234
  """
98
235
  import matplotlib.pyplot as plt
99
236
  import mrcfile
@@ -106,11 +243,32 @@ def save_comparison_visualization(
106
243
 
107
244
  # Create title
108
245
  name = original_path.name
109
- short_name = name[:60] + "..." if len(name) > 60 else name
110
- title = f"Lattice Subtraction Comparison: {short_name}"
246
+ short_name = name[:50] + "..." if len(name) > 50 else name
247
+ title = f"Lattice Subtraction: {short_name}"
248
+
249
+ # Try to compute threshold curve if config available
250
+ try:
251
+ if config is None:
252
+ # Create default config for threshold computation
253
+ from .config import Config
254
+ config = Config(pixel_ang=0.56) # Default K3 pixel size
255
+
256
+ # Compute threshold optimization curve
257
+ thresholds, quality_scores, optimal_threshold, optimal_quality = \
258
+ compute_threshold_curve(original, config)
259
+
260
+ # Create 4-panel figure with threshold curve
261
+ fig = create_comparison_figure_with_threshold(
262
+ original, processed,
263
+ thresholds, quality_scores,
264
+ optimal_threshold, optimal_quality,
265
+ title=title, dpi=dpi
266
+ )
267
+ except Exception as e:
268
+ # Fallback to 3-panel if threshold computation fails
269
+ logger.debug(f"Could not compute threshold curve: {e}, using 3-panel view")
270
+ fig = create_comparison_figure(original, processed, title=title, dpi=dpi)
111
271
 
112
- # Create and save figure
113
- fig = create_comparison_figure(original, processed, title=title, dpi=dpi)
114
272
  output_path.parent.mkdir(parents=True, exist_ok=True)
115
273
  fig.savefig(output_path, dpi=dpi, bbox_inches='tight')
116
274
  plt.close(fig)
@@ -124,9 +282,11 @@ def generate_visualizations(
124
282
  pattern: str = "*.mrc",
125
283
  dpi: int = 150,
126
284
  show_progress: bool = True,
285
+ limit: Optional[int] = None,
286
+ config = None,
127
287
  ) -> Tuple[int, int]:
128
288
  """
129
- Generate comparison visualizations for all processed images in a directory.
289
+ Generate comparison visualizations for processed images in a directory.
130
290
 
131
291
  Args:
132
292
  input_dir: Directory containing original MRC files
@@ -136,6 +296,8 @@ def generate_visualizations(
136
296
  pattern: Glob pattern for finding processed files
137
297
  dpi: Resolution for output images
138
298
  show_progress: Show progress bar
299
+ limit: Maximum number of visualizations to generate (None = all)
300
+ config: Config object for threshold computation (optional)
139
301
 
140
302
  Returns:
141
303
  Tuple of (successful_count, total_count)
@@ -152,6 +314,12 @@ def generate_visualizations(
152
314
  logger.warning(f"No processed files found matching '{prefix}{pattern}' in {output_dir}")
153
315
  return 0, 0
154
316
 
317
+ # Apply limit if specified
318
+ total_available = len(output_files)
319
+ if limit is not None and limit > 0:
320
+ output_files = output_files[:limit]
321
+ logger.info(f"Limiting to {limit} visualizations (of {total_available} available)")
322
+
155
323
  successful = 0
156
324
  total = len(output_files)
157
325
 
@@ -184,11 +352,12 @@ def generate_visualizations(
184
352
  successful += 1
185
353
  continue
186
354
 
187
- # Generate visualization
355
+ # Generate visualization with 4-panel layout
188
356
  save_comparison_visualization(
189
357
  original_path=input_path,
190
358
  processed_path=processed_path,
191
359
  output_path=viz_path,
360
+ config=config,
192
361
  dpi=dpi,
193
362
  )
194
363
  successful += 1