From 1cd6bd0f4f48b5ce766b979451fd745070791d52 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 4 Dec 2023 11:20:13 -0500 Subject: [PATCH 1/3] Make cutoff+resolution plotting optional for frc/fsc --- src/aspire/image/image.py | 5 ++- src/aspire/utils/resolution_estimation.py | 37 ++++++++++++++++------- src/aspire/volume/volume.py | 4 ++- tests/test_fourier_correlation.py | 8 +++-- 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/aspire/image/image.py b/src/aspire/image/image.py index c85fc8188f..11ee7f412a 100644 --- a/src/aspire/image/image.py +++ b/src/aspire/image/image.py @@ -571,7 +571,7 @@ def show(self, columns=5, figsize=(20, 10), colorbar=True): plt.show() - def frc(self, other, cutoff, pixel_size=None, method="fft", plot=False): + def frc(self, other, cutoff=None, pixel_size=None, method="fft", plot=False): r""" Compute the Fourier ring correlation between two images. @@ -586,6 +586,9 @@ def frc(self, other, cutoff, pixel_size=None, method="fft", plot=False): :param other: `Image` instance to compare. :param cutoff: Cutoff value, traditionally `.143`. + Default `None` implies `cutoff=1` and excludes + plotting cutoff line. + :param pixel_size: Pixel size in angstrom. Default `None` implies unit in pixels, equivalent to pixel_size=1. :param method: Selects either 'fft' (on cartesian grid), diff --git a/src/aspire/utils/resolution_estimation.py b/src/aspire/utils/resolution_estimation.py index 5a54c269b8..b7a06f68df 100644 --- a/src/aspire/utils/resolution_estimation.py +++ b/src/aspire/utils/resolution_estimation.py @@ -250,7 +250,11 @@ def analyze_correlations(self, cutoff): Convert from the Fourier correlations to frequencies and resolution. :param cutoff: Cutoff value, traditionally `.143`. + Note `cutoff=None` evaluates as `cutoff=1`. """ + # Handle optional cutoff plotting. + if cutoff is None: + cutoff = 1 cutoff = float(cutoff) if not (0 <= cutoff <= 1): @@ -289,17 +293,25 @@ def _freq(self, k): # Similar to wavenumbers. Larger is higher frequency. return k / (self.L * self.pixel_size) - def plot(self, cutoff, save_to_file=False, labels=None): + def plot(self, cutoff=None, save_to_file=False, labels=None): """ Generates a Fourier correlation plot. :param cutoff: Cutoff value, traditionally `.143`. + Default `None` implies `cutoff=1` and excludes + plotting cutoff line. :param save_to_file: Optionally, save plot to file. Defaults False, enabled by providing a string filename. User is responsible for providing reasonable filename. See `https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.savefig.html`. """ - cutoff = float(cutoff) + + # Handle optional cutoff plotting. + _plot_cutoff = True + if cutoff is None: + cutoff = 1 + _plot_cutoff = False + if not (0 <= cutoff <= 1): raise ValueError("Supplied correlation `cutoff` not in [0,1], {cutoff}") @@ -344,17 +356,20 @@ def plot(self, cutoff, save_to_file=False, labels=None): _label = labels[i] plt.plot(freqs_units, line, label=_label) - # Display cutoff - plt.axhline(y=cutoff, color="r", linestyle="--", label=f"cutoff={cutoff}") estimated_resolution = self.analyze_correlations(cutoff)[0] - # Display resolution - plt.axvline( - x=estimated_resolution, - color="b", - linestyle=":", - label=f"Resolution={estimated_resolution:.3f}", - ) + # Display cutoff + if _plot_cutoff: + plt.axhline(y=cutoff, color="r", linestyle="--", label=f"cutoff={cutoff}") + + # Display resolution + plt.axvline( + x=estimated_resolution, + color="b", + linestyle=":", + label=f"Resolution={estimated_resolution:.3f}", + ) + # x-axis decreasing plt.gca().invert_xaxis() plt.legend(title=f"Method: {self.method}") diff --git a/src/aspire/volume/volume.py b/src/aspire/volume/volume.py index 5993a1dd4e..cc29710045 100644 --- a/src/aspire/volume/volume.py +++ b/src/aspire/volume/volume.py @@ -549,7 +549,7 @@ def load(cls, filename, permissive=True, dtype=None, symmetry_group=None): return cls(loaded_data, symmetry_group=symmetry_group, dtype=dtype) - def fsc(self, other, cutoff, pixel_size=None, method="fft", plot=False): + def fsc(self, other, cutoff=None, pixel_size=None, method="fft", plot=False): r""" Compute the Fourier shell correlation between two volumes. @@ -564,6 +564,8 @@ def fsc(self, other, cutoff, pixel_size=None, method="fft", plot=False): :param other: `Volume` instance to compare. :param cutoff: Cutoff value, traditionally `.143`. + Default `None` implies `cutoff=1` and excludes + plotting cutoff line. :param pixel_size: Pixel size in angstrom. Default `None` implies unit in pixels, equivalent to pixel_size=1. :param method: Selects either 'fft' (on cartesian grid), diff --git a/tests/test_fourier_correlation.py b/tests/test_fourier_correlation.py index 476e2f5548..282089ce12 100644 --- a/tests/test_fourier_correlation.py +++ b/tests/test_fourier_correlation.py @@ -145,9 +145,10 @@ def test_frc_img_plot(image_fixture): _ = img_a.frc(img_n, pixel_size=1, cutoff=0.143, plot=True) # Plot to file + # Also tests `cutoff=None` with tempfile.TemporaryDirectory() as tmp_input_dir: file_path = os.path.join(tmp_input_dir, "img_frc_curve.png") - img_a.frc(img_n, pixel_size=1, cutoff=0.143, plot=file_path) + img_a.frc(img_n, pixel_size=1, cutoff=None, plot=file_path) assert os.path.exists(file_path) @@ -204,9 +205,10 @@ def test_fsc_vol_plot(volume_fixture): _ = vol_a.fsc(vol_b, pixel_size=1, cutoff=0.5, plot=True) # Plot to file + # Also tests `cutoff=None` with tempfile.TemporaryDirectory() as tmp_input_dir: - file_path = os.path.join(tmp_input_dir, "img_fsc_curve.png") - vol_a.fsc(vol_b, pixel_size=1, cutoff=0.143, plot=file_path) + file_path = os.path.join(tmp_input_dir, "vol_fsc_curve.png") + vol_a.fsc(vol_b, pixel_size=1, cutoff=None, plot=file_path) assert os.path.exists(file_path) From 31f4c26773dcf03998cc61c153dad3e0eb5f1ada Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 4 Dec 2023 11:26:54 -0500 Subject: [PATCH 2/3] Resolve new Flake8 concern, unprinted tuple --- gallery/tutorials/tutorials/image_class.py | 2 +- tox.ini | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/gallery/tutorials/tutorials/image_class.py b/gallery/tutorials/tutorials/image_class.py index 36d689ddc0..ca4df61961 100644 --- a/gallery/tutorials/tutorials/image_class.py +++ b/gallery/tutorials/tutorials/image_class.py @@ -14,7 +14,7 @@ file_path = os.path.join(os.path.dirname(os.getcwd()), "data", "monuments.npy") img_data = np.load(file_path) -img_data.shape, img_data.dtype +print(img_data.shape, img_data.dtype) # %% # Create an Image Instance diff --git a/tox.ini b/tox.ini index 723d15daf5..8c4a6d9687 100644 --- a/tox.ini +++ b/tox.ini @@ -72,6 +72,7 @@ per-file-ignores = gallery/tutorials/pipeline_demo.py: T201 gallery/tutorials/turorials/data_downloader.py: E402 gallery/tutorials/tutorials/ctf.py: T201, E402 + gallery/tutorials/tutorials/image_class.py: T201 gallery/tutorials/tutorials/micrograph_source.py: T201, E402 gallery/tutorials/tutorials/weighted_volume_estimation.py: T201, E402 # Ignore Sphinx gallery builds From 63c84df8b819e9348d79ea5056f112e6bf368a4c Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 4 Dec 2023 15:38:24 -0500 Subject: [PATCH 3/3] Cleanup warnings --- src/aspire/utils/resolution_estimation.py | 11 ++++++++--- tests/test_utils.py | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/aspire/utils/resolution_estimation.py b/src/aspire/utils/resolution_estimation.py index b7a06f68df..8c67a6f949 100644 --- a/src/aspire/utils/resolution_estimation.py +++ b/src/aspire/utils/resolution_estimation.py @@ -2,6 +2,7 @@ This module contains code for estimating resolution achieved by reconstructions. """ import logging +import warnings import matplotlib.pyplot as plt import numpy as np @@ -44,7 +45,7 @@ def __init__(self, a, b, pixel_size=None, method="fft"): Default `None` implies "pixel" units. :param method: Selects either 'fft' (on Cartesian grid), or 'nufft' (on polar grid). Defaults to 'fft'. - 7""" + """ # Sanity checks if not hasattr(self, "dim"): @@ -275,8 +276,12 @@ def analyze_correlations(self, cutoff): # Convert indices to frequency (as 1/angstrom) frequencies = self._freq(c_ind) - # Convert to resolution in angstrom, smaller is higher frequency. - self._resolutions = 1 / frequencies + with warnings.catch_warnings(): + # When using high cutoff (eg. 1) it is possible `frequencies` + # contains 0; capture and ignore that division warning. + warnings.filterwarnings("ignore", r".*divide by zero.*") + # Convert to resolution in angstrom, smaller is higher frequency. + self._resolutions = 1 / frequencies return self._resolutions diff --git a/tests/test_utils.py b/tests/test_utils.py index ecd87c0122..ffad5bc9f6 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -395,7 +395,7 @@ def matplotlib_no_gui(): # Save and restore current warnings list. with warnings.catch_warnings(): - warnings.filterwarnings("ignore", "Matplotlib is currently using agg") + warnings.filterwarnings("ignore", r"Matplotlib is currently using agg.*") yield