From a78aa71a669774564859dc77c7d0a1467193a55f Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Wed, 25 Mar 2026 10:03:50 +0000 Subject: [PATCH 1/2] Rename PlotterInterface to Plotter; extract subplot functions; add docstrings; update docs - Rename plotter_interface.py -> plotter.py and PlotterInterface* -> Plotter* across all dataset types (imaging, interferometer, quantity, ellipse) - Extract inline matplotlib subplot code into standalone functions in autoarray and autogalaxy; replace all callers with function calls - Add numpy-style docstrings to all Plotter methods and standalone plot functions - Update docs/api/plot.rst to list new standalone aplt functions instead of old classes - Update docs/overview/overview_1_start_here.rst to use new aplt.plot_array / plot_grid API - Rename test_plotter_interface* -> test_plotter* to match source rename - Fix interferometer subplot_dataset filename (was subplot_dirty_images) Co-Authored-By: Claude Sonnet 4.6 --- .../{plotter_interface.py => plotter.py} | 61 +++++++- autogalaxy/config/visualize/plots.yaml | 12 +- .../{plotter_interface.py => plotter.py} | 50 ++++--- autogalaxy/ellipse/model/visualizer.py | 6 +- autogalaxy/imaging/fit_imaging.py | 4 +- .../{plotter_interface.py => plotter.py} | 137 +++++++++++------- autogalaxy/imaging/model/visualizer.py | 10 +- autogalaxy/imaging/plot/fit_imaging_plots.py | 37 +++++ .../interferometer/fit_interferometer.py | 4 +- .../{plotter_interface.py => plotter.py} | 73 +++++++--- autogalaxy/interferometer/model/visualizer.py | 18 +-- .../{plotter_interface.py => plotter.py} | 29 +++- autogalaxy/quantity/model/visualizer.py | 8 +- docs/api/plot.rst | 66 ++++++--- docs/overview/overview_1_start_here.rst | 28 ++-- ...t_plotter_interface.py => test_plotter.py} | 16 +- ...ace_imaging.py => test_plotter_imaging.py} | 16 +- ...eter.py => test_plotter_interferometer.py} | 14 +- ...e_quantity.py => test_plotter_quantity.py} | 12 +- 19 files changed, 418 insertions(+), 183 deletions(-) rename autogalaxy/analysis/{plotter_interface.py => plotter.py} (75%) rename autogalaxy/ellipse/model/{plotter_interface.py => plotter.py} (68%) rename autogalaxy/imaging/model/{plotter_interface.py => plotter.py} (59%) rename autogalaxy/interferometer/model/{plotter_interface.py => plotter.py} (64%) rename autogalaxy/quantity/model/{plotter_interface.py => plotter.py} (59%) rename test_autogalaxy/analysis/{test_plotter_interface.py => test_plotter.py} (82%) rename test_autogalaxy/imaging/model/{test_plotter_interface_imaging.py => test_plotter_imaging.py} (76%) rename test_autogalaxy/interferometer/model/{test_plotter_interface_interferometer.py => test_plotter_interferometer.py} (69%) rename test_autogalaxy/quantity/model/{test_plotter_interface_quantity.py => test_plotter_quantity.py} (63%) diff --git a/autogalaxy/analysis/plotter_interface.py b/autogalaxy/analysis/plotter.py similarity index 75% rename from autogalaxy/analysis/plotter_interface.py rename to autogalaxy/analysis/plotter.py index 96f4ef52d..704411dce 100644 --- a/autogalaxy/analysis/plotter_interface.py +++ b/autogalaxy/analysis/plotter.py @@ -38,8 +38,25 @@ def plot_setting(section: Union[List[str], str], name: str) -> bool: return setting(section, name) -class PlotterInterface: +class Plotter: def __init__(self, image_path: Union[Path, str], title_prefix: str = None): + """ + Base class for all plotters, which output visualizations during a model fit. + + The methods of the `Plotter` are called throughout a non-linear search via the + `Analysis` class `visualize` method. + + The images output by the `Plotter` are customized using the file + `config/visualize/plots.yaml`. + + Parameters + ---------- + image_path + The path on the hard-disk to the `image` folder of the non-linear search + results where all visualizations are saved. + title_prefix + An optional string prefixed to every plot title. + """ from pathlib import Path self.image_path = Path(image_path) @@ -49,9 +66,11 @@ def __init__(self, image_path: Union[Path, str], title_prefix: str = None): @property def fmt(self) -> List[str]: + """The output file format(s) read from ``config/visualize/plots.yaml``.""" return conf.instance["visualize"]["plots"]["subplot_format"] def output_from(self) -> aplt.Output: + """Return an ``autoarray`` ``Output`` object pointed at ``image_path``.""" return aplt.Output(path=self.image_path, format=self.fmt) def galaxies( @@ -59,6 +78,21 @@ def galaxies( galaxies: List[Galaxy], grid: aa.type.Grid2DLike, ): + """ + Output visualization of a list of galaxies. + + Controlled by the ``[galaxies]`` section of ``config/visualize/plots.yaml``. + Outputs include galaxy image subplots and, when enabled, a FITS file of each + galaxy image. + + Parameters + ---------- + galaxies + The list of galaxies to visualize. + grid + A 2D grid of (y, x) arc-second coordinates used to evaluate each galaxy + image. + """ galaxies = Galaxies(galaxies=galaxies) def should_plot(name): @@ -94,6 +128,18 @@ def should_plot(name): hdu_list.writeto(self.image_path / "galaxy_images.fits", overwrite=True) def inversion(self, inversion: aa.Inversion): + """ + Output visualization of an ``Inversion``. + + Controlled by the ``[inversion]`` section of ``config/visualize/plots.yaml``. + When enabled, outputs a scatter-plot of each mapper's source-plane + reconstruction and a CSV of the reconstruction values and noise map. + + Parameters + ---------- + inversion + The inversion whose reconstruction is visualized. + """ def should_plot(name): return plot_setting(section="inversion", name=name) @@ -144,6 +190,19 @@ def should_plot(name): ) def adapt_images(self, adapt_images: AdaptImages): + """ + Output visualization of adapt images from a previous model-fit search. + + Controlled by the ``[adapt]`` section of ``config/visualize/plots.yaml``. + Outputs a subplot of the per-galaxy adapt images and, when enabled, FITS files + of the adapt images and image-plane mesh grids. + + Parameters + ---------- + adapt_images + The adapt images containing per-galaxy images used to drive adaptive mesh + and regularization schemes. + """ def should_plot(name): return plot_setting(section="adapt", name=name) diff --git a/autogalaxy/config/visualize/plots.yaml b/autogalaxy/config/visualize/plots.yaml index 94adf1563..4790ade3a 100644 --- a/autogalaxy/config/visualize/plots.yaml +++ b/autogalaxy/config/visualize/plots.yaml @@ -13,11 +13,11 @@ subplot_format: [png] # Output format of all subplots, can be png, pdf or both (e.g. [png, pdf]) fits_are_zoomed: false # If true, output .fits files are zoomed in on the center of the unmasked region image, saving hard-disk space. -dataset: # Settings for plots of all datasets (e.g. ImagingPlotter, InterferometerPlotter). +dataset: # Settings for plots of all datasets (e.g. Imaging, Interferometer). subplot_dataset: true # Plot subplot containing all dataset quantities (e.g. the data, noise-map, etc.)? fits_dataset: true # Output a .fits file containing the dataset data, noise-map and other quantities? -fit: # Settings for plots of all fits (e.g. FitImagingPlotter, FitInterferometerPlotter). +fit: # Settings for plots of all fits (e.g. FitImaging, FitInterferometer). subplot_fit: true # Plot subplot of all fit quantities for any dataset (e.g. the model data, residual-map, etc.)? subplot_fit_log10: true # Plot subplot of all fit quantities for any dataset using log10 color maps (e.g. the model data, residual-map, etc.)? subplot_of_galaxies: false # Plot subplot of the model-image, subtracted image and other quantities of each galaxy? @@ -26,9 +26,9 @@ fit: # Settings for plots of all fits (e.g fits_galaxy_images : true # Output a .fits file containing the images (e.g. without PSF convolution) of every galaxy? fits_model_galaxy_images : true # Output a .fits file containing the model images (e.g. with PSF convolution) of every galaxy? -fit_imaging: {} # Settings for plots of fits to imaging datasets (e.g. FitImagingPlotter). +fit_imaging: {} # Settings for plots of fits to imaging datasets (e.g. FitImaging). -galaxies: # Settings for plots of galaxies (e.g. GalaxiesPlotter). +galaxies: # Settings for plots of galaxies (e.g. Galaxies). subplot_galaxies: true # Plot subplot of all quantities in each galaxies group (e.g. images, convergence)? subplot_galaxy_images: false # Plot subplot of the image of each galaxy in the model? fits_galaxy_images: false # Output a .fits file containing images of every galaxy? @@ -42,7 +42,7 @@ adapt: # Settings for plots of adapt images subplot_adapt_images: true # Plot subplot showing each adapt image used for adaptive pixelization? fits_adapt_images: true # Output a .fits file containing the adapt images used for adaptive pixelization? -fit_interferometer: # Settings for plots of fits to interferometer datasets (e.g. FitInterferometerPlotter). +fit_interferometer: # Settings for plots of fits to interferometer datasets (e.g. FitInterferometer). subplot_fit_dirty_images: false # Plot subplot of the dirty-images of all interferometer datasets? subplot_fit_real_space: false # Plot subplot of the real-space images of all interferometer datasets? fits_dirty_images: true # output dirty_images.fits showing the dirty image, noise-map, model-data, resiual-map, normalized residual map and chi-squared map? @@ -53,4 +53,4 @@ fit_ellipse: # Settings for plots of ellipse fitti data_no_ellipse: true # Plot the data without the black data ellipses, which obscure noisy data? ellipse_residuals: true # Plot the residuals of the ellipse fit? -fit_quantity: {} # Settings for plots of fit quantities (e.g. FitQuantityPlotter). \ No newline at end of file +fit_quantity: {} # Settings for plots of fit quantities (e.g. FitQuantity). \ No newline at end of file diff --git a/autogalaxy/ellipse/model/plotter_interface.py b/autogalaxy/ellipse/model/plotter.py similarity index 68% rename from autogalaxy/ellipse/model/plotter_interface.py rename to autogalaxy/ellipse/model/plotter.py index cba51b6be..0adbba405 100644 --- a/autogalaxy/ellipse/model/plotter_interface.py +++ b/autogalaxy/ellipse/model/plotter.py @@ -1,35 +1,39 @@ -import matplotlib.pyplot as plt from typing import List from autoconf.fitsable import hdu_list_for_output_from import autoarray as aa -import autoarray.plot as aplt + +from autoarray.dataset.plot.imaging_plots import subplot_imaging from autogalaxy.ellipse.fit_ellipse import FitEllipse from autogalaxy.ellipse.plot import fit_ellipse_plots -from autogalaxy.analysis.plotter_interface import PlotterInterface, plot_setting -from autogalaxy.plot.plot_utils import plot_array, _save_subplot +from autogalaxy.analysis.plotter import Plotter, plot_setting -class PlotterInterfaceEllipse(PlotterInterface): +class PlotterEllipse(Plotter): def imaging(self, dataset: aa.Imaging): + """ + Output visualization of an ``Imaging`` dataset for ellipse fitting. + + Controlled by the ``[dataset]`` / ``[imaging]`` sections of + ``config/visualize/plots.yaml``. Outputs a subplot of the imaging data + and a FITS file containing the mask, data, and noise-map arrays. + + Parameters + ---------- + dataset + The imaging dataset to visualize. + """ def should_plot(name): return plot_setting(section=["dataset", "imaging"], name=name) if should_plot("subplot_dataset"): - panels = [ - (dataset.data, "Data"), - (dataset.noise_map, "Noise Map"), - (dataset.signal_to_noise_map, "Signal-To-Noise Map"), - ] - n = len(panels) - fig, axes = plt.subplots(1, n, figsize=(7 * n, 7)) - axes_flat = list(axes.flatten()) if n > 1 else [axes] - for i, (array, title) in enumerate(panels): - plot_array(array, title, ax=axes_flat[i]) - plt.tight_layout() - _save_subplot(fig, self.image_path, "subplot_dataset", self.fmt) + subplot_imaging( + dataset, + output_path=self.image_path, + output_format=self.fmt, + ) image_list = [ dataset.data.native, @@ -52,6 +56,18 @@ def fit_ellipse( self, fit_list: List[FitEllipse], ): + """ + Output visualization of a list of ``FitEllipse`` objects. + + Controlled by the ``[fit]`` / ``[fit_ellipse]`` sections of + ``config/visualize/plots.yaml``. Outputs data images with ellipse + overlays, ellipse residual plots, and a combined fit subplot. + + Parameters + ---------- + fit_list + The list of ellipse fits to visualize (one per ellipse). + """ def should_plot(name): return plot_setting(section=["fit", "fit_ellipse"], name=name) diff --git a/autogalaxy/ellipse/model/visualizer.py b/autogalaxy/ellipse/model/visualizer.py index 4c8f80dcd..eb657bc4f 100644 --- a/autogalaxy/ellipse/model/visualizer.py +++ b/autogalaxy/ellipse/model/visualizer.py @@ -2,7 +2,7 @@ from autoarray import exc -from autogalaxy.ellipse.model.plotter_interface import PlotterInterfaceEllipse +from autogalaxy.ellipse.model.plotter import PlotterEllipse class VisualizerEllipse(af.Visualizer): @@ -27,7 +27,7 @@ def visualize_before_fit( the imaging data. """ - plotter = PlotterInterfaceEllipse( + plotter = PlotterEllipse( image_path=paths.image_path, title_prefix=analysis.title_prefix ) @@ -63,7 +63,7 @@ def visualize( """ fit_list = analysis.fit_list_from(instance=instance) - plotter = PlotterInterfaceEllipse( + plotter = PlotterEllipse( image_path=paths.image_path, title_prefix=analysis.title_prefix ) plotter.imaging(dataset=analysis.dataset) diff --git a/autogalaxy/imaging/fit_imaging.py b/autogalaxy/imaging/fit_imaging.py index 667a4eaae..1d198bec6 100644 --- a/autogalaxy/imaging/fit_imaging.py +++ b/autogalaxy/imaging/fit_imaging.py @@ -325,7 +325,7 @@ def galaxies_linear_light_profiles_to_light_profiles(self) -> List[Galaxy]: The galaxy list where all linear light profiles have been converted to ordinary light profiles, where their `intensity` values are set to the values inferred by this fit. - This is typically used for visualization, because linear light profiles cannot be used in `LightProfilePlotter` - or `GalaxyPlotter` objects. + This is typically used for visualization, because linear light profiles cannot be used in `LightProfile` + or `Galaxy` objects. """ return self.model_obj_linear_light_profiles_to_light_profiles diff --git a/autogalaxy/imaging/model/plotter_interface.py b/autogalaxy/imaging/model/plotter.py similarity index 59% rename from autogalaxy/imaging/model/plotter_interface.py rename to autogalaxy/imaging/model/plotter.py index e2604a333..8a24db78e 100644 --- a/autogalaxy/imaging/model/plotter_interface.py +++ b/autogalaxy/imaging/model/plotter.py @@ -1,19 +1,35 @@ -import matplotlib.pyplot as plt from pathlib import Path from typing import List from autoconf.fitsable import hdu_list_for_output_from import autoarray as aa -import autoarray.plot as aplt + +from autoarray.dataset.plot.imaging_plots import subplot_imaging, subplot_imaging_dataset_list from autogalaxy.imaging.fit_imaging import FitImaging from autogalaxy.imaging.plot import fit_imaging_plots -from autogalaxy.analysis.plotter_interface import PlotterInterface, plot_setting -from autogalaxy.plot.plot_utils import _save_subplot, plot_array +from autogalaxy.imaging.plot.fit_imaging_plots import subplot_fit_imaging_list +from autogalaxy.analysis.plotter import Plotter, plot_setting def fits_to_fits(should_plot, image_path: Path, fit: FitImaging): + """ + Write fit residuals and galaxy images from a ``FitImaging`` to FITS files. + + Controlled by the ``fits_fit``, ``fits_galaxy_images``, and + ``fits_model_galaxy_images`` toggles in ``config/visualize/plots.yaml``. + + Parameters + ---------- + should_plot + A callable that accepts a plot-name string and returns ``True`` when + that plot is enabled in the config. + image_path + Directory where the FITS files are written. + fit + The imaging fit whose arrays are saved to FITS. + """ if should_plot("fits_fit"): image_list = [ fit.model_data.native_for_fits, @@ -56,28 +72,30 @@ def fits_to_fits(should_plot, image_path: Path, fit: FitImaging): hdu_list.writeto(image_path / "model_galaxy_images.fits", overwrite=True) -class PlotterInterfaceImaging(PlotterInterface): +class PlotterImaging(Plotter): def imaging(self, dataset: aa.Imaging): + """ + Output visualization of an ``Imaging`` dataset. + + Controlled by the ``[dataset]`` / ``[imaging]`` sections of + ``config/visualize/plots.yaml``. Outputs a subplot of the imaging data + and, when enabled, a FITS file containing the mask, data, noise map, PSF, + and over-sample-size arrays. + + Parameters + ---------- + dataset + The imaging dataset to visualize. + """ def should_plot(name): return plot_setting(section=["dataset", "imaging"], name=name) if should_plot("subplot_dataset"): - panels = [ - (dataset.data, "Data"), - (dataset.noise_map, "Noise Map"), - (dataset.signal_to_noise_map, "Signal-To-Noise Map"), - ] - try: - panels.append((dataset.psf.kernel, "PSF")) - except Exception: - pass - n = len(panels) - fig, axes = plt.subplots(1, n, figsize=(7 * n, 7)) - axes_flat = list(axes.flatten()) if n > 1 else [axes] - for i, (array, title) in enumerate(panels): - plot_array(array, title, ax=axes_flat[i]) - plt.tight_layout() - _save_subplot(fig, self.image_path, "subplot_dataset", self.fmt) + subplot_imaging( + dataset, + output_path=self.image_path, + output_format=self.fmt, + ) if should_plot("fits_dataset"): image_list = [ @@ -97,6 +115,21 @@ def should_plot(name): hdu_list.writeto(self.image_path / "dataset.fits", overwrite=True) def fit_imaging(self, fit: FitImaging, quick_update: bool = False): + """ + Output visualization of a ``FitImaging`` object. + + Controlled by the ``[fit]`` / ``[fit_imaging]`` sections of + ``config/visualize/plots.yaml``. Outputs the main fit subplot and, + optionally, per-galaxy subplots and FITS residual files. + + Parameters + ---------- + fit + The imaging fit to visualize. + quick_update + When ``True`` only the essential ``subplot_fit`` is written; all + other outputs are skipped. + """ def should_plot(name): return plot_setting(section=["fit", "fit_imaging"], name=name) @@ -123,41 +156,47 @@ def should_plot(name): fits_to_fits(should_plot=should_plot, image_path=self.image_path, fit=fit) def imaging_combined(self, dataset_list: List[aa.Imaging]): + """ + Output visualization of a list of ``Imaging`` datasets from a combined analysis. + + Controlled by the ``[dataset]`` / ``[imaging]`` sections of + ``config/visualize/plots.yaml``. Outputs a combined subplot with one + row per dataset. + + Parameters + ---------- + dataset_list + The list of imaging datasets to visualize. + """ def should_plot(name): return plot_setting(section=["dataset", "imaging"], name=name) if should_plot("subplot_dataset"): - n = len(dataset_list) - fig, axes = plt.subplots(n, 4, figsize=(28, 7 * n)) - if n == 1: - axes = [axes] - - for i, dataset in enumerate(dataset_list): - plot_array(dataset.data, "Data", ax=axes[i][0]) - plot_array(dataset.noise_map, "Noise Map", ax=axes[i][1]) - plot_array(dataset.signal_to_noise_map, "Signal-To-Noise Map", ax=axes[i][2]) - - plt.tight_layout() - _save_subplot(fig, self.image_path, "subplot_dataset_combined", self.fmt) + subplot_imaging_dataset_list( + dataset_list, + output_path=self.image_path, + output_format=self.fmt, + ) def fit_imaging_combined(self, fit_list: List[FitImaging]): + """ + Output visualization of a list of ``FitImaging`` objects from a combined analysis. + + Controlled by the ``[fit]`` / ``[fit_imaging]`` sections of + ``config/visualize/plots.yaml``. Outputs a combined subplot with one + row per fit. + + Parameters + ---------- + fit_list + The list of imaging fits to visualize. + """ def should_plot(name): return plot_setting(section=["fit", "fit_imaging"], name=name) - output = self.output_from() - if should_plot("subplot_fit"): - n = len(fit_list) - fig, axes = plt.subplots(n, 5, figsize=(35, 7 * n)) - if n == 1: - axes = [axes] - - for i, fit in enumerate(fit_list): - plot_array(fit.data, "Data", ax=axes[i][0]) - plot_array(fit.signal_to_noise_map, "Signal-To-Noise Map", ax=axes[i][1]) - plot_array(fit.model_data, "Model Image", ax=axes[i][2]) - plot_array(fit.normalized_residual_map, "Normalized Residual Map", ax=axes[i][3]) - plot_array(fit.chi_squared_map, "Chi-Squared Map", ax=axes[i][4]) - - plt.tight_layout() - _save_subplot(fig, self.image_path, "subplot_fit_combined", self.fmt) + subplot_fit_imaging_list( + fit_list, + output_path=self.image_path, + output_format=self.fmt, + ) diff --git a/autogalaxy/imaging/model/visualizer.py b/autogalaxy/imaging/model/visualizer.py index 75da56ef0..311bdaef2 100644 --- a/autogalaxy/imaging/model/visualizer.py +++ b/autogalaxy/imaging/model/visualizer.py @@ -4,7 +4,7 @@ from autoarray import exc -from autogalaxy.imaging.model.plotter_interface import PlotterInterfaceImaging +from autogalaxy.imaging.model.plotter import PlotterImaging logger = logging.getLogger(__name__) @@ -33,7 +33,7 @@ def visualize_before_fit( dataset = analysis.dataset - plotter = PlotterInterfaceImaging( + plotter = PlotterImaging( image_path=paths.image_path, title_prefix=analysis.title_prefix ) @@ -78,7 +78,7 @@ def visualize( """ fit = analysis.fit_from(instance=instance) - plotter = PlotterInterfaceImaging( + plotter = PlotterImaging( image_path=paths.image_path, title_prefix=analysis.title_prefix ) # Quick Update only, skips everything after @@ -130,7 +130,7 @@ def visualize_before_fit_combined( if analyses is None: return - plotter = PlotterInterfaceImaging( + plotter = PlotterImaging( image_path=paths.image_path, title_prefix=analyses[0].title_prefix ) @@ -168,7 +168,7 @@ def visualize_combined( if analyses is None: return - plotter = PlotterInterfaceImaging( + plotter = PlotterImaging( image_path=paths.image_path, title_prefix=analyses[0].title_prefix ) diff --git a/autogalaxy/imaging/plot/fit_imaging_plots.py b/autogalaxy/imaging/plot/fit_imaging_plots.py index cfa223663..8d76e8bd6 100644 --- a/autogalaxy/imaging/plot/fit_imaging_plots.py +++ b/autogalaxy/imaging/plot/fit_imaging_plots.py @@ -126,3 +126,40 @@ def subplot_of_galaxy( plt.tight_layout() _save_subplot(fig, output_path, f"subplot_of_galaxy_{galaxy_index}", output_format) + + +def subplot_fit_imaging_list( + fit_list, + output_path=None, + output_filename: str = "subplot_fit_combined", + output_format="png", +): + """ + nĂ—5 subplot summarising a list of ``FitImaging`` objects. + + Each row shows: Data | Signal-To-Noise Map | Model Image | + Normalized Residual Map | Chi-Squared Map + + Parameters + ---------- + fit_list + List of ``FitImaging`` instances. + output_path + Directory to save the figure. ``None`` calls ``plt.show()``. + output_filename + Base filename without extension. + output_format + File format string or list, e.g. ``"png"`` or ``["png"]``. + """ + n = len(fit_list) + fig, axes = plt.subplots(n, 5, figsize=(35, 7 * n)) + if n == 1: + axes = [axes] + for i, fit in enumerate(fit_list): + plot_array(array=fit.data, title="Data", ax=axes[i][0]) + plot_array(array=fit.signal_to_noise_map, title="Signal-To-Noise Map", ax=axes[i][1]) + plot_array(array=fit.model_data, title="Model Image", ax=axes[i][2]) + plot_array(array=fit.normalized_residual_map, title="Normalized Residual Map", ax=axes[i][3]) + plot_array(array=fit.chi_squared_map, title="Chi-Squared Map", ax=axes[i][4]) + plt.tight_layout() + _save_subplot(fig, output_path, output_filename, output_format) diff --git a/autogalaxy/interferometer/fit_interferometer.py b/autogalaxy/interferometer/fit_interferometer.py index 1663d697f..9f20b0c9f 100644 --- a/autogalaxy/interferometer/fit_interferometer.py +++ b/autogalaxy/interferometer/fit_interferometer.py @@ -217,7 +217,7 @@ def galaxies_linear_light_profiles_to_light_profiles(self) -> List[Galaxy]: The galaxies where all linear light profiles have been converted to ordinary light profiles, where their `intensity` values are set to the values inferred by this fit. - This is typically used for visualization, because linear light profiles cannot be used in `LightProfilePlotter` - or `GalaxyPlotter` objects. + This is typically used for visualization, because linear light profiles cannot be used in `LightProfile` + or `Galaxy` objects. """ return self.model_obj_linear_light_profiles_to_light_profiles diff --git a/autogalaxy/interferometer/model/plotter_interface.py b/autogalaxy/interferometer/model/plotter.py similarity index 64% rename from autogalaxy/interferometer/model/plotter_interface.py rename to autogalaxy/interferometer/model/plotter.py index f62a2414d..3a6b30512 100644 --- a/autogalaxy/interferometer/model/plotter_interface.py +++ b/autogalaxy/interferometer/model/plotter.py @@ -3,11 +3,12 @@ from autoconf.fitsable import hdu_list_for_output_from import autoarray as aa -import autoarray.plot as aplt + +from autoarray.dataset.plot.interferometer_plots import subplot_interferometer_dirty_images from autogalaxy.interferometer.fit_interferometer import FitInterferometer from autogalaxy.interferometer.plot import fit_interferometer_plots -from autogalaxy.analysis.plotter_interface import PlotterInterface, plot_setting +from autogalaxy.analysis.plotter import Plotter, plot_setting def fits_to_fits( @@ -15,6 +16,22 @@ def fits_to_fits( image_path: Path, fit: FitInterferometer, ): + """ + Write galaxy images and dirty-image residuals from a ``FitInterferometer`` to FITS files. + + Controlled by the ``fits_galaxy_images`` and ``fits_dirty_images`` toggles in + ``config/visualize/plots.yaml``. + + Parameters + ---------- + should_plot + A callable that accepts a plot-name string and returns ``True`` when + that plot is enabled in the config. + image_path + Directory where the FITS files are written. + fit + The interferometer fit whose arrays are saved to FITS. + """ if should_plot("fits_galaxy_images"): image_list = [image.native_for_fits for image in fit.galaxy_image_dict.values()] @@ -56,27 +73,31 @@ def fits_to_fits( hdu_list.writeto(image_path / "fit_dirty_images.fits", overwrite=True) -class PlotterInterfaceInterferometer(PlotterInterface): +class PlotterInterferometer(Plotter): def interferometer(self, dataset: aa.Interferometer): + """ + Output visualization of an ``Interferometer`` dataset. + + Controlled by the ``[dataset]`` / ``[interferometer]`` sections of + ``config/visualize/plots.yaml``. Outputs a dirty-image subplot and, + when enabled, a FITS file containing the mask, visibilities, noise map, + and UV-wavelengths arrays. + + Parameters + ---------- + dataset + The interferometer dataset to visualize. + """ def should_plot(name): return plot_setting(section=["dataset", "interferometer"], name=name) if should_plot("subplot_dataset"): - from autogalaxy.plot.plot_utils import plot_array, _save_subplot - import matplotlib.pyplot as plt - - panels = [ - (dataset.dirty_image, "Dirty Image"), - (dataset.dirty_noise_map, "Dirty Noise Map"), - (dataset.dirty_signal_to_noise_map, "Dirty Signal-To-Noise Map"), - ] - n = len(panels) - fig, axes = plt.subplots(1, n, figsize=(7 * n, 7)) - axes_flat = list(axes.flatten()) - for i, (array, title) in enumerate(panels): - plot_array(array, title, ax=axes_flat[i]) - plt.tight_layout() - _save_subplot(fig, self.image_path, "subplot_dataset", self.fmt) + subplot_interferometer_dirty_images( + dataset, + output_path=self.image_path, + output_filename="subplot_dataset", + output_format=self.fmt[0] if isinstance(self.fmt, (list, tuple)) else self.fmt, + ) if should_plot("fits_dataset"): @@ -98,6 +119,22 @@ def fit_interferometer( fit: FitInterferometer, quick_update: bool = False, ): + """ + Output visualization of a ``FitInterferometer`` object. + + Controlled by the ``[fit]`` / ``[fit_interferometer]`` sections of + ``config/visualize/plots.yaml``. Outputs the main fit subplot, a + dirty-images subplot, and when enabled a real-space subplot and FITS + residual files. + + Parameters + ---------- + fit + The interferometer fit to visualize. + quick_update + When ``True`` only the essential subplots are written; the + real-space subplot and FITS outputs are skipped. + """ def should_plot(name): return plot_setting(section=["fit", "fit_interferometer"], name=name) diff --git a/autogalaxy/interferometer/model/visualizer.py b/autogalaxy/interferometer/model/visualizer.py index f5cc1d977..0fe1a8912 100644 --- a/autogalaxy/interferometer/model/visualizer.py +++ b/autogalaxy/interferometer/model/visualizer.py @@ -2,8 +2,8 @@ import autofit as af -from autogalaxy.interferometer.model.plotter_interface import ( - PlotterInterfaceInterferometer, +from autogalaxy.interferometer.model.plotter import ( + PlotterInterferometer, ) from autogalaxy import exc @@ -32,14 +32,14 @@ def visualize_before_fit( the imaging data. """ - PlotterInterface = PlotterInterfaceInterferometer( + plotter = PlotterInterferometer( image_path=paths.image_path, title_prefix=analysis.title_prefix ) - PlotterInterface.interferometer(dataset=analysis.interferometer) + plotter.interferometer(dataset=analysis.interferometer) if analysis.adapt_images is not None: - PlotterInterface.adapt_images(adapt_images=analysis.adapt_images) + plotter.adapt_images(adapt_images=analysis.adapt_images) @staticmethod def visualize( @@ -80,12 +80,12 @@ def visualize( """ fit = analysis.fit_from(instance=instance) - plotter_interface = PlotterInterfaceInterferometer( + plotter = PlotterInterferometer( image_path=paths.image_path, title_prefix=analysis.title_prefix ) try: - plotter_interface.fit_interferometer( + plotter.fit_interferometer( fit=fit, quick_update=quick_update, ) @@ -97,14 +97,14 @@ def visualize( galaxies = fit.galaxies_linear_light_profiles_to_light_profiles - plotter_interface.galaxies( + plotter.galaxies( galaxies=galaxies, grid=fit.grids.lp, ) if fit.inversion is not None: try: - plotter_interface.inversion( + plotter.inversion( inversion=fit.inversion, ) except (IndexError, exc.InversionException): diff --git a/autogalaxy/quantity/model/plotter_interface.py b/autogalaxy/quantity/model/plotter.py similarity index 59% rename from autogalaxy/quantity/model/plotter_interface.py rename to autogalaxy/quantity/model/plotter.py index 1039c16df..089e054bb 100644 --- a/autogalaxy/quantity/model/plotter_interface.py +++ b/autogalaxy/quantity/model/plotter.py @@ -3,11 +3,22 @@ from autogalaxy.quantity.dataset_quantity import DatasetQuantity from autogalaxy.quantity.fit_quantity import FitQuantity from autogalaxy.quantity.plot import fit_quantity_plots -from autogalaxy.analysis.plotter_interface import PlotterInterface, plot_setting +from autogalaxy.analysis.plotter import Plotter, plot_setting -class PlotterInterfaceQuantity(PlotterInterface): +class PlotterQuantity(Plotter): def dataset_quantity(self, dataset: DatasetQuantity): + """ + Output visualization of a ``DatasetQuantity`` dataset. + + Writes a FITS file containing the mask, data, and noise-map arrays + regardless of config toggles (always-on output for quantity datasets). + + Parameters + ---------- + dataset + The quantity dataset to visualize. + """ image_list = [ dataset.data.native_for_fits, dataset.noise_map.native_for_fits, @@ -33,6 +44,20 @@ def fit_quantity( fit: FitQuantity, fit_quantity_plots_module=None, ): + """ + Output visualization of a ``FitQuantity`` object. + + Controlled by the ``[fit_quantity]`` section of + ``config/visualize/plots.yaml``. Outputs a fit subplot. + + Parameters + ---------- + fit + The quantity fit to visualize. + fit_quantity_plots_module + Optional override for the plots module; defaults to + ``autogalaxy.quantity.plot.fit_quantity_plots``. + """ def should_plot(name): return plot_setting(section="fit_quantity", name=name) diff --git a/autogalaxy/quantity/model/visualizer.py b/autogalaxy/quantity/model/visualizer.py index 30a5feeca..32483aabd 100644 --- a/autogalaxy/quantity/model/visualizer.py +++ b/autogalaxy/quantity/model/visualizer.py @@ -2,7 +2,7 @@ import autofit as af -from autogalaxy.quantity.model.plotter_interface import PlotterInterfaceQuantity +from autogalaxy.quantity.model.plotter import PlotterQuantity class VisualizerQuantity(af.Visualizer): @@ -28,7 +28,7 @@ def visualize_before_fit( """ dataset = analysis.dataset - plotter = PlotterInterfaceQuantity( + plotter = PlotterQuantity( image_path=paths.image_path, title_prefix=analysis.title_prefix ) @@ -70,7 +70,7 @@ def visualize( fit = analysis.fit_quantity_for_instance(instance=instance) - PlotterInterface = PlotterInterfaceQuantity( + plotter = PlotterQuantity( image_path=paths.image_path, title_prefix=analysis.title_prefix ) - PlotterInterface.fit_quantity(fit=fit) + plotter.fit_quantity(fit=fit) diff --git a/docs/api/plot.rst b/docs/api/plot.rst index 6325e8383..103929050 100644 --- a/docs/api/plot.rst +++ b/docs/api/plot.rst @@ -19,27 +19,57 @@ Create figures and subplots showing quantities of standard **PyAutoGalaxy** obje .. currentmodule:: autogalaxy.plot +**Basic Plot Functions:** + +.. autosummary:: + :toctree: _autosummary + + plot_array + plot_grid + +**Galaxy and Light / Mass Profile Subplots:** + +.. autosummary:: + :toctree: _autosummary + + subplot_galaxy_light_profiles + subplot_galaxy_mass_profiles + subplot_basis_image + subplot_galaxies + subplot_galaxy_images + subplot_adapt_images + +**Imaging Fit Subplots:** + +.. autosummary:: + :toctree: _autosummary + + subplot_fit_imaging + subplot_fit_imaging_of_galaxy + +**Interferometer Fit Subplots:** + +.. autosummary:: + :toctree: _autosummary + + subplot_fit_interferometer + subplot_fit_dirty_images + subplot_fit_real_space + +**Quantity Fit Subplots:** + +.. autosummary:: + :toctree: _autosummary + + subplot_fit_quantity + +**Ellipse Fit Subplots:** + .. autosummary:: :toctree: _autosummary - :template: custom-class-template.rst - :recursive: - Array2DPlotter - Grid2DPlotter - MapperPlotter - YX1DPlotter - InversionPlotter - ImagingPlotter - InterferometerPlotter - LightProfilePlotter - GalaxyPlotter - FitImagingPlotter - FitInterferometerPlotter - GalaxiesPlotter - FitImagingPlotter - FitInterferometerPlotter - MultiFigurePlotter - MultiYX1DPlotter + subplot_fit_ellipse + subplot_ellipse_errors Non-linear Search Plotters [aplt] --------------------------------- diff --git a/docs/overview/overview_1_start_here.rst b/docs/overview/overview_1_start_here.rst index 6f05ae2e7..e61d29daf 100644 --- a/docs/overview/overview_1_start_here.rst +++ b/docs/overview/overview_1_start_here.rst @@ -51,8 +51,7 @@ We make and plot a uniform Cartesian grid: pixel_scales=0.05, # The pixel-scale describes the conversion from pixel units to arc-seconds. ) - grid_plotter = aplt.Grid2DPlotter(grid=grid) - grid_plotter.figure_2d() + aplt.plot_grid(grid=grid, title="Uniform Grid") The ``Grid2D`` looks like this: @@ -95,19 +94,16 @@ Plotting In-built plotting methods are provided for plotting objects and their properties, like the image of a light profile we just created. -By using a ``LightProfilePlotter`` to plot the light profile's image, the figured is improved. +By using ``aplt.plot_array`` to plot the light profile's image, the figure is improved. -Its axis units are scaled to arc-seconds, a color-bar is added, its given a descriptive labels, etc. +Its axis units are scaled to arc-seconds, a color-bar is added, descriptive labels are included, etc. The plot module is highly customizable and designed to make it straight forward to create clean and informative figures for fits to large datasets. .. code:: python - light_profile_plotter = aplt.LightProfilePlotter( - light_profile=sersic_light_profile, grid=grid - ) - light_profile_plotter.figures_2d(image=True) + aplt.plot_array(array=sersic_light_profile.image_2d_from(grid=grid), title="Sersic Light Profile Image") The light profile appears as follows: @@ -136,12 +132,11 @@ a bulge and disk component. ) -The ``GalaxyPlotter`` object plots the image of the galaxy, which is the sum of its bulge and disk light profiles. +We can plot the image of the galaxy, which is the sum of its bulge and disk light profiles: .. code:: python - galaxy_plotter = aplt.GalaxyPlotter(galaxy=galaxy, grid=grid) - galaxy_plotter.figures_2d(image=True) + aplt.plot_array(array=galaxy.image_2d_from(grid=grid), title="Galaxy Image") The galaxy, with both a bulge and disk, appears as follows: @@ -149,12 +144,11 @@ The galaxy, with both a bulge and disk, appears as follows: :width: 600 :alt: Alternative text -One example of the plotter's customizability is the ability to plot the individual light profiles of the galaxy -on a subplot. +The individual light profiles of the galaxy can be plotted on a subplot: .. code:: python - galaxy_plotter.subplot_of_light_profiles(image=True) + aplt.subplot_galaxy_light_profiles(galaxy=galaxy, grid=grid) The light profiles appear as follows: @@ -186,8 +180,7 @@ the galaxy is used below where the ``Sersic`` is passed directly to the ``Galaxy galaxies=[galaxy, galaxy_1], ) - galaxies_plotter = aplt.GalaxiesPlotter(galaxies=galaxies, grid=grid) - galaxies_plotter.figures_2d(image=True) + aplt.plot_array(array=galaxies.image_2d_from(grid=grid), title="Galaxies Image") .. image:: https://raw.githubusercontent.com/Jammy2211/PyAutoGalaxy/main/docs/overview/images/overview_1/4_image_2d.png :width: 600 @@ -252,8 +245,7 @@ To further illustrate this, we create a merging galaxy system with 4 star formin galaxies = ag.Galaxies(galaxies=[galaxy_0, galaxy_1]) - galaxies_plotter = aplt.GalaxiesPlotter(galaxies=galaxies, grid=grid) - galaxies_plotter.figures_2d(image=True) + aplt.plot_array(array=galaxies.image_2d_from(grid=grid), title="Galaxies Image") The image of the merging galaxy system appears as follows: diff --git a/test_autogalaxy/analysis/test_plotter_interface.py b/test_autogalaxy/analysis/test_plotter.py similarity index 82% rename from test_autogalaxy/analysis/test_plotter_interface.py rename to test_autogalaxy/analysis/test_plotter.py index b883e74ae..f2131f719 100644 --- a/test_autogalaxy/analysis/test_plotter_interface.py +++ b/test_autogalaxy/analysis/test_plotter.py @@ -6,13 +6,13 @@ import autogalaxy as ag -from autogalaxy.analysis.plotter_interface import PlotterInterface +from autogalaxy.analysis.plotter import Plotter directory = path.dirname(path.abspath(__file__)) @pytest.fixture(name="plot_path") -def make_plotter_interface_plotter_setup(): +def make_plotter_plotter_setup(): return path.join("{}".format(directory), "files") @@ -20,9 +20,9 @@ def test__galaxies(masked_imaging_7x7, galaxies_7x7, plot_path, plot_patch): if path.exists(plot_path): shutil.rmtree(plot_path) - plotter_interface = PlotterInterface(image_path=plot_path) + plotter = Plotter(image_path=plot_path) - plotter_interface.galaxies( + plotter.galaxies( galaxies=galaxies_7x7, grid=masked_imaging_7x7.grids.lp, ) @@ -45,9 +45,9 @@ def test__inversion( if path.exists(plot_path): shutil.rmtree(plot_path) - plotter_interface = PlotterInterface(image_path=plot_path) + plotter = Plotter(image_path=plot_path) - plotter_interface.inversion( + plotter.inversion( inversion=rectangular_inversion_7x7_3x3, ) @@ -80,13 +80,13 @@ def test__adapt_images( plot_path, plot_patch, ): - plotter_interface = PlotterInterface(image_path=plot_path) + plotter = Plotter(image_path=plot_path) adapt_images = ag.AdaptImages( galaxy_name_image_dict=adapt_galaxy_name_image_dict_7x7, ) - plotter_interface.adapt_images( + plotter.adapt_images( adapt_images=adapt_images, ) diff --git a/test_autogalaxy/imaging/model/test_plotter_interface_imaging.py b/test_autogalaxy/imaging/model/test_plotter_imaging.py similarity index 76% rename from test_autogalaxy/imaging/model/test_plotter_interface_imaging.py rename to test_autogalaxy/imaging/model/test_plotter_imaging.py index 58f59479e..a6dcc958c 100644 --- a/test_autogalaxy/imaging/model/test_plotter_interface_imaging.py +++ b/test_autogalaxy/imaging/model/test_plotter_imaging.py @@ -3,13 +3,13 @@ import pytest import autogalaxy as ag -from autogalaxy.imaging.model.plotter_interface import PlotterInterfaceImaging +from autogalaxy.imaging.model.plotter import PlotterImaging directory = path.dirname(path.abspath(__file__)) @pytest.fixture(name="plot_path") -def make_plotter_interface_plotter_setup(): +def make_plotter_plotter_setup(): return path.join("{}".format(directory), "files") @@ -17,9 +17,9 @@ def test__imaging(imaging_7x7, plot_path, plot_patch): if path.exists(plot_path): shutil.rmtree(plot_path) - plotter_interface = PlotterInterfaceImaging(image_path=plot_path) + plotter = PlotterImaging(image_path=plot_path) - plotter_interface.imaging(dataset=imaging_7x7) + plotter.imaging(dataset=imaging_7x7) assert path.join(plot_path, "subplot_dataset.png") in plot_patch.paths @@ -34,7 +34,7 @@ def test__imaging_combined(imaging_7x7, plot_path, plot_patch): if path.exists(plot_path): shutil.rmtree(plot_path) - visualizer = PlotterInterfaceImaging(image_path=plot_path) + visualizer = PlotterImaging(image_path=plot_path) visualizer.imaging_combined(dataset_list=[imaging_7x7, imaging_7x7]) @@ -50,9 +50,9 @@ def test__fit_imaging( if path.exists(plot_path): shutil.rmtree(plot_path) - plotter_interface = PlotterInterfaceImaging(image_path=plot_path) + plotter = PlotterImaging(image_path=plot_path) - plotter_interface.fit_imaging( + plotter.fit_imaging( fit=fit_imaging_x2_galaxy_inversion_7x7, ) @@ -75,7 +75,7 @@ def test__fit_imaging_combined( if path.exists(plot_path): shutil.rmtree(plot_path) - visualizer = PlotterInterfaceImaging(image_path=plot_path) + visualizer = PlotterImaging(image_path=plot_path) visualizer.fit_imaging_combined(fit_list=2 * [fit_imaging_x2_galaxy_inversion_7x7]) diff --git a/test_autogalaxy/interferometer/model/test_plotter_interface_interferometer.py b/test_autogalaxy/interferometer/model/test_plotter_interferometer.py similarity index 69% rename from test_autogalaxy/interferometer/model/test_plotter_interface_interferometer.py rename to test_autogalaxy/interferometer/model/test_plotter_interferometer.py index dd89a3274..04ed039b5 100644 --- a/test_autogalaxy/interferometer/model/test_plotter_interface_interferometer.py +++ b/test_autogalaxy/interferometer/model/test_plotter_interferometer.py @@ -3,22 +3,22 @@ import autogalaxy as ag -from autogalaxy.interferometer.model.plotter_interface import ( - PlotterInterfaceInterferometer, +from autogalaxy.interferometer.model.plotter import ( + PlotterInterferometer, ) directory = path.dirname(path.abspath(__file__)) @pytest.fixture(name="plot_path") -def make_plotter_interface_plotter_setup(): +def make_plotter_plotter_setup(): return path.join("{}".format(directory), "files") def test__interferometer(interferometer_7, plot_path, plot_patch): - plotter_interface = PlotterInterfaceInterferometer(image_path=plot_path) + plotter = PlotterInterferometer(image_path=plot_path) - plotter_interface.interferometer(dataset=interferometer_7) + plotter.interferometer(dataset=interferometer_7) assert path.join(plot_path, "subplot_dataset.png") in plot_patch.paths @@ -35,9 +35,9 @@ def test__fit_interferometer( plot_path, plot_patch, ): - PlotterInterface = PlotterInterfaceInterferometer(image_path=plot_path) + plotter = PlotterInterferometer(image_path=plot_path) - PlotterInterface.fit_interferometer( + plotter.fit_interferometer( fit=fit_interferometer_x2_galaxy_inversion_7x7, ) diff --git a/test_autogalaxy/quantity/model/test_plotter_interface_quantity.py b/test_autogalaxy/quantity/model/test_plotter_quantity.py similarity index 63% rename from test_autogalaxy/quantity/model/test_plotter_interface_quantity.py rename to test_autogalaxy/quantity/model/test_plotter_quantity.py index eae1a139b..39a9d17fc 100644 --- a/test_autogalaxy/quantity/model/test_plotter_interface_quantity.py +++ b/test_autogalaxy/quantity/model/test_plotter_quantity.py @@ -4,13 +4,13 @@ import autogalaxy as ag -from autogalaxy.quantity.model.plotter_interface import PlotterInterfaceQuantity +from autogalaxy.quantity.model.plotter import PlotterQuantity directory = path.dirname(path.abspath(__file__)) @pytest.fixture(name="plot_path") -def make_plotter_interface_plotter_setup(): +def make_plotter_plotter_setup(): return path.join("{}".format(directory), "files") @@ -22,9 +22,9 @@ def test__dataset( if path.exists(plot_path): shutil.rmtree(plot_path) - PlotterInterface = PlotterInterfaceQuantity(image_path=plot_path) + plotter = PlotterQuantity(image_path=plot_path) - PlotterInterface.dataset_quantity(dataset=dataset_quantity_7x7_array_2d) + plotter.dataset_quantity(dataset=dataset_quantity_7x7_array_2d) image = ag.ndarray_via_fits_from( file_path=path.join(plot_path, "dataset.fits"), hdu=1 @@ -42,8 +42,8 @@ def test__fit_quantity( if path.exists(plot_path): shutil.rmtree(plot_path) - PlotterInterface = PlotterInterfaceQuantity(image_path=plot_path) + plotter = PlotterQuantity(image_path=plot_path) - PlotterInterface.fit_quantity(fit=fit_quantity_7x7_array_2d) + plotter.fit_quantity(fit=fit_quantity_7x7_array_2d) assert path.join(plot_path, "subplot_fit.png") not in plot_patch.paths From 1c75ed4c4399b1bae3619beb3b1b7b8e1e629640 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Wed, 25 Mar 2026 10:06:33 +0000 Subject: [PATCH 2/2] Re-export autoarray dataset subplot functions from autogalaxy.plot Makes subplot_imaging, subplot_imaging_dataset_list, and subplot_interferometer_dirty_images accessible via import autogalaxy.plot as aplt. Co-Authored-By: Claude Sonnet 4.6 --- autogalaxy/plot/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/autogalaxy/plot/__init__.py b/autogalaxy/plot/__init__.py index 7d94d7c62..69d766f51 100644 --- a/autogalaxy/plot/__init__.py +++ b/autogalaxy/plot/__init__.py @@ -1,5 +1,11 @@ from autogalaxy.plot.plot_utils import plot_array, plot_grid +from autoarray.dataset.plot.imaging_plots import ( + subplot_imaging, + subplot_imaging_dataset_list, +) +from autoarray.dataset.plot.interferometer_plots import subplot_interferometer_dirty_images + from autogalaxy.profiles.plot.basis_plots import subplot_image as subplot_basis_image from autogalaxy.galaxy.plot.galaxy_plots import (