diff --git a/autolens/imaging/model/plotter.py b/autolens/imaging/model/plotter.py index a3b7722ae..3f8481bc3 100644 --- a/autolens/imaging/model/plotter.py +++ b/autolens/imaging/model/plotter.py @@ -13,7 +13,6 @@ from autolens.imaging.fit_imaging import FitImaging from autolens.imaging.plot.fit_imaging_plots import ( subplot_fit, - subplot_fit_quick, subplot_fit_log10, subplot_of_planes, subplot_tracer_from_fit, @@ -76,8 +75,11 @@ def should_plot(name): source_plane_lines, source_plane_line_colors, ) + # Quick updates write the normal fit subplot (plain `fit.png`, final + # plane as source) regardless of plane count, so the live display + # always has one canonical filename to refresh. if quick_update: - subplot_fit_quick( + subplot_fit( fit, output_path=output_path, output_format=fmt, image_plane_lines=ip_lines, image_plane_line_colors=ip_colors, source_plane_lines=sp_lines, source_plane_line_colors=sp_colors, diff --git a/autolens/imaging/plot/fit_imaging_plots.py b/autolens/imaging/plot/fit_imaging_plots.py index f1975a436..443eb0743 100644 --- a/autolens/imaging/plot/fit_imaging_plots.py +++ b/autolens/imaging/plot/fit_imaging_plots.py @@ -312,94 +312,6 @@ def subplot_fit( save_figure(fig, path=output_path, filename=f"fit{plane_index_tag}", format=output_format) -def subplot_fit_quick( - fit, - output_path: Optional[str] = None, - output_format: str = None, - colormap: Optional[str] = None, - image_plane_lines=None, - image_plane_line_colors=None, - source_plane_lines=None, - source_plane_line_colors=None, - title_prefix: str = None, -): - """ - Produce a 6-panel quick-update subplot summarising an imaging fit. - - Arranges the following panels in a 2 × 3 grid: - - * Data - * Model image - * Normalised residual map (symmetric scale) - * Lens-light-subtracted image - * Source model image - * Source plane image - - Uses the standard ``plot_array`` / ``_plot_source_plane`` for - consistent styling with arcsecond axes. Fit properties are now - ``@cached_property`` so repeated access is cheap. - - For single-plane tracers the function delegates to - :func:`subplot_fit_x1_plane`. - """ - if len(fit.tracer.planes) == 1: - return subplot_fit_x1_plane( - fit, output_path=output_path, - output_format=output_format, colormap=colormap, - title_prefix=title_prefix, - ) - - final_plane_index = len(fit.tracer.planes) - 1 - source_vmax = _get_source_vmax(fit) - - _pf = (lambda t: f"{title_prefix.rstrip()} {t}") if title_prefix else (lambda t: t) - fig, axes = subplots(2, 3, figsize=conf_subplot_figsize(2, 3)) - axes_flat = list(axes.flatten()) - - # Top row: Data, Model Image, Normalized Residual Map - plot_array(array=fit.data, ax=axes_flat[0], - title=_pf("Data"), colormap=colormap) - - plot_array(array=fit.model_data, ax=axes_flat[1], - title=_pf("Model Image"), colormap=colormap) - - plot_array(array=fit.normalized_residual_map, ax=axes_flat[2], - title=_pf("Normalized Residual"), colormap=colormap, - symmetric=True) - - # Bottom row: Lens Light Subtracted, Source Model Image, Source Plane - try: - subtracted = fit.subtracted_images_of_planes_list[final_plane_index] - except (IndexError, AttributeError): - subtracted = None - if subtracted is not None: - plot_array(array=subtracted, ax=axes_flat[3], - title=_pf("Lens Light Subtracted"), colormap=colormap, - vmin=0.0 if source_vmax else None, vmax=source_vmax) - else: - axes_flat[3].axis("off") - - try: - source_model = fit.model_images_of_planes_list[final_plane_index] - except (IndexError, AttributeError): - source_model = None - if source_model is not None: - plot_array(array=source_model, ax=axes_flat[4], - title=_pf("Source Model Image"), colormap=colormap, - vmax=source_vmax) - else: - axes_flat[4].axis("off") - - _plot_source_plane( - fit, axes_flat[5], final_plane_index, zoom_to_brightest=False, - colormap=colormap, title=_pf("Source Plane"), vmax=source_vmax, - ) - - hide_unused_axes(axes_flat) - tight_layout() - save_figure(fig, path=output_path, filename="fit_quick", format=output_format, dpi=100) - - def subplot_fit_x1_plane( fit, output_path: Optional[str] = None, @@ -945,28 +857,6 @@ def subplot_fit_combined( save_figure(fig, path=output_path, filename="fit_combined", format=output_format) -def subplot_fit_combined_quick( - fit_list: List, - output_path: Optional[str] = None, - output_format: str = None, - colormap: Optional[str] = None, - title_prefix: str = None, -): - """ - Placeholder quick-update subplot for combined multi-dataset imaging fits. - - Currently delegates to :func:`subplot_fit_combined` but writes - ``fit_quick.png`` so the live display picks it up. - """ - subplot_fit_combined( - fit_list, - output_path=output_path, - output_format=output_format, - colormap=colormap, - title_prefix=title_prefix, - ) - - def subplot_fit_combined_log10( fit_list: List, output_path: Optional[str] = None, diff --git a/autolens/interferometer/model/plotter.py b/autolens/interferometer/model/plotter.py index 45ccaddfa..87bde0daf 100644 --- a/autolens/interferometer/model/plotter.py +++ b/autolens/interferometer/model/plotter.py @@ -12,7 +12,6 @@ from autolens.interferometer.fit_interferometer import FitInterferometer from autolens.interferometer.plot.fit_interferometer_plots import ( subplot_fit, - subplot_fit_quick, subplot_fit_dirty_images, subplot_fit_interferometer_combined, subplot_fit_real_space, @@ -70,7 +69,7 @@ def should_plot(name): source_plane_lines, source_plane_line_colors, ) - if should_plot("subplot_fit"): + if should_plot("subplot_fit") or quick_update: subplot_fit( fit, output_path=output_path, output_format=fmt, image_plane_lines=ip_lines, image_plane_line_colors=ip_colors, @@ -78,6 +77,9 @@ def should_plot(name): title_prefix=self.title_prefix, ) + if quick_update: + return + if plot_setting(section="tracer", name="subplot_tracer"): subplot_tracer_from_fit( fit, output_path=output_path, output_format=fmt, @@ -86,13 +88,6 @@ def should_plot(name): title_prefix=self.title_prefix, ) - if quick_update: - subplot_fit_quick( - fit, output_path=output_path, output_format=fmt, - title_prefix=self.title_prefix, - ) - return - if should_plot("subplot_fit_dirty_images"): subplot_fit_dirty_images( fit, output_path=output_path, output_format=fmt, diff --git a/autolens/interferometer/plot/fit_interferometer_plots.py b/autolens/interferometer/plot/fit_interferometer_plots.py index 43b18facb..01162898e 100644 --- a/autolens/interferometer/plot/fit_interferometer_plots.py +++ b/autolens/interferometer/plot/fit_interferometer_plots.py @@ -307,73 +307,6 @@ def subplot_fit_dirty_images( save_figure(fig, path=output_path, filename="fit_dirty_images", format=output_format) -def subplot_fit_quick( - fit, - output_path: Optional[str] = None, - output_format: str = None, - colormap: Optional[str] = None, - title_prefix: str = None, -): - """ - Produce a 6-panel quick-update subplot for an interferometer fit. - - Arranges the following panels in a 2 × 3 grid: - - * Dirty Image (data) - * Dirty Model Image - * Dirty Normalised Residual Map - * Visibility Normalised Residual (Real) vs UV distance - * Visibility Normalised Residual (Imag) vs UV distance - * Source plane image / reconstruction - - Uses the standard ``plot_array`` / ``plot_yx`` / ``_plot_source_plane`` - for consistent styling. Dirty images are passed directly as autoarray - ``Array2D`` objects so axes show arcsecond coordinates. - """ - _pf = (lambda t: f"{title_prefix.rstrip()} {t}") if title_prefix else (lambda t: t) - fig, axes = subplots(2, 3, figsize=conf_subplot_figsize(2, 3)) - axes_flat = list(axes.flatten()) - - # Top row: Dirty Image, Dirty Model Image, Dirty Normalized Residual - plot_array(array=fit.dirty_image, ax=axes_flat[0], - title=_pf("Dirty Image"), colormap=colormap) - - plot_array(array=fit.dirty_model_image, ax=axes_flat[1], - title=_pf("Dirty Model Image"), colormap=colormap) - - plot_array(array=fit.dirty_normalized_residual_map, ax=axes_flat[2], - title=_pf("Dirty Norm Residual"), colormap=colormap, - symmetric=True) - - # Bottom row: Visibility residuals (Real/Imag scatter) + Source Plane - norm_resid_vis = np.asarray(fit.normalized_residual_map) - uv_dist = np.asarray(fit.dataset.uv_distances) / 1e3 - - plot_yx( - y=np.real(norm_resid_vis), x=uv_dist, ax=axes_flat[3], - title=_pf("Vis Norm Resid (Real)"), - xtick_suffix='"', ytick_suffix=r"$\sigma$", - plot_axis_type="scatter", - ) - - plot_yx( - y=np.imag(norm_resid_vis), x=uv_dist, ax=axes_flat[4], - title=_pf("Vis Norm Resid (Imag)"), - xtick_suffix='"', ytick_suffix=r"$\sigma$", - plot_axis_type="scatter", - ) - - # Source plane: reuse _plot_source_plane (handles both parametric and pixelized) - final_plane_index = len(fit.tracer.planes) - 1 - _plot_source_plane( - fit, axes_flat[5], final_plane_index, zoom_to_brightest=False, - colormap=colormap, title=_pf("Source Plane"), - ) - - tight_layout() - save_figure(fig, path=output_path, filename="fit_quick", format=output_format, dpi=100) - - def subplot_fit_interferometer_combined( fit_list, output_path: Optional[str] = None, diff --git a/autolens/point/model/plotter.py b/autolens/point/model/plotter.py index a4f24cc46..af743fe38 100644 --- a/autolens/point/model/plotter.py +++ b/autolens/point/model/plotter.py @@ -5,7 +5,6 @@ from autolens.point.fit.dataset import FitPointDataset from autolens.point.plot.fit_point_plots import subplot_fit as subplot_fit_point -from autolens.point.plot.fit_point_plots import subplot_fit_quick as subplot_fit_quick_point from autolens.point.dataset import PointDataset from autolens.point.plot.point_dataset_plots import subplot_dataset @@ -80,8 +79,12 @@ def should_plot(name): ) if quick_update: - subplot_fit_quick_point( + subplot_fit_point( fit, output_path=output_path, output_format=fmt, + image_plane_lines=ip_lines, + image_plane_line_colors=ip_colors, + source_plane_lines=sp_lines, + source_plane_line_colors=sp_colors, title_prefix=self.title_prefix, ) return diff --git a/autolens/point/plot/fit_point_plots.py b/autolens/point/plot/fit_point_plots.py index 416c32f8b..72adea7bc 100644 --- a/autolens/point/plot/fit_point_plots.py +++ b/autolens/point/plot/fit_point_plots.py @@ -86,45 +86,3 @@ def subplot_fit( save_figure(fig, path=output_path, filename="fit", format=output_format) -def subplot_fit_quick( - fit, - output_path: Optional[str] = None, - output_format: str = None, - title_prefix: str = None, -): - """ - Produce a single-panel quick-update subplot for a `FitPointDataset`. - - Shows the observed positions with the model-predicted positions - overlaid in red. A minimal progress view for quick updates during - sampling — will be expanded in future. - """ - from autogalaxy.util.plot_utils import plot_grid - - obs_grid = np.array( - fit.dataset.positions.array - if hasattr(fit.dataset.positions, "array") - else fit.dataset.positions - ) - model_grid = np.array( - fit.positions.model_data.array - if hasattr(fit.positions.model_data, "array") - else fit.positions.model_data - ) - - _prefix = f"{title_prefix.rstrip()} " if title_prefix else "" - fig, ax = subplots(1, 1, figsize=conf_subplot_figsize(1, 1)) - - plot_grid( - grid=obs_grid, - ax=ax, - title=f"{_prefix}{fit.dataset.name} Positions", - output_path=None, - output_filename=None, - output_format=output_format, - ) - ax.scatter(model_grid[:, 1], model_grid[:, 0], c="r", s=20, zorder=5, label="Model") - ax.legend(fontsize=7, loc="upper right") - - tight_layout() - save_figure(fig, path=output_path, filename="fit_quick", format=output_format, dpi=100) diff --git a/autolens/weak/model/plotter.py b/autolens/weak/model/plotter.py index 485dd9cd4..fc35dc772 100644 --- a/autolens/weak/model/plotter.py +++ b/autolens/weak/model/plotter.py @@ -5,7 +5,6 @@ from autolens.weak.fit import FitWeak from autolens.weak.plot.weak_dataset_plots import subplot_weak_dataset from autolens.weak.plot.fit_weak_plots import subplot_fit_weak -from autolens.weak.plot.fit_weak_plots import subplot_fit_quick as subplot_fit_quick_weak class PlotterWeak(Plotter): @@ -42,7 +41,7 @@ def fit_weak(self, fit: FitWeak, quick_update: bool = False): fit The maximum log likelihood `FitWeak` of the non-linear search. quick_update - If `True`, a lighter-weight quick-update subplot is output instead of the full fit subplot. + If `True`, the fit subplot is always output and all other outputs are skipped. """ def should_plot(name): @@ -52,7 +51,7 @@ def should_plot(name): fmt = self.fmt if quick_update: - subplot_fit_quick_weak( + subplot_fit_weak( fit, output_path=output_path, output_format=fmt, diff --git a/autolens/weak/plot/fit_weak_plots.py b/autolens/weak/plot/fit_weak_plots.py index 74b8c8a17..875b6684f 100644 --- a/autolens/weak/plot/fit_weak_plots.py +++ b/autolens/weak/plot/fit_weak_plots.py @@ -160,27 +160,6 @@ def plot_chi_squared_map( ) -def subplot_fit_quick( - fit, - output_path: Optional[str] = None, - output_format: Optional[str] = None, - title_prefix: Optional[str] = None, -): - """ - Placeholder quick-update subplot for a ``FitWeak``. - - Currently delegates to the full ``subplot_fit_weak``. Will be - replaced with a lighter-weight render in future. - """ - subplot_fit_weak( - fit, - output_path=output_path, - output_filename="fit_quick", - output_format=output_format, - title_prefix=title_prefix, - ) - - def subplot_fit_weak( fit, output_path: Optional[str] = None, diff --git a/test_autolens/imaging/model/test_plotter_imaging.py b/test_autolens/imaging/model/test_plotter_imaging.py index 22354a3c4..9761a8642 100644 --- a/test_autolens/imaging/model/test_plotter_imaging.py +++ b/test_autolens/imaging/model/test_plotter_imaging.py @@ -41,6 +41,22 @@ def test__fit_imaging( assert image.shape == (5, 5) +def test__fit_imaging__quick_update__writes_normal_fit_subplot_only( + fit_imaging_x2_plane_inversion_7x7, plot_path, plot_patch +): + if plot_path.exists(): + shutil.rmtree(plot_path) + + plotter = PlotterImaging(image_path=plot_path) + + plotter.fit_imaging(fit=fit_imaging_x2_plane_inversion_7x7, quick_update=True) + + assert str(plot_path / "fit.png") in plot_patch.paths + assert str(plot_path / "fit_quick.png") not in plot_patch.paths + assert str(plot_path / "tracer.png") not in plot_patch.paths + assert str(plot_path / "fit_log10.png") not in plot_patch.paths + + def test__fit_imaging_combined( fit_imaging_x2_plane_inversion_7x7, plot_path, plot_patch ): diff --git a/test_autolens/interferometer/model/test_plotter_interferometer.py b/test_autolens/interferometer/model/test_plotter_interferometer.py index 0d293dff1..8be31f7a9 100644 --- a/test_autolens/interferometer/model/test_plotter_interferometer.py +++ b/test_autolens/interferometer/model/test_plotter_interferometer.py @@ -16,6 +16,22 @@ def make_plotter_plotter_setup(): return directory / "files" +def test__fit_interferometer__quick_update__writes_normal_fit_subplot_only( + fit_interferometer_x2_plane_7x7, + plot_path, + plot_patch, +): + plotter = PlotterInterferometer(image_path=plot_path) + + plotter.fit_interferometer( + fit=fit_interferometer_x2_plane_7x7, quick_update=True + ) + + assert str(plot_path / "fit.png") in plot_patch.paths + assert str(plot_path / "fit_quick.png") not in plot_patch.paths + assert str(plot_path / "fit_dirty_images.png") not in plot_patch.paths + + def test__fit_interferometer( fit_interferometer_x2_plane_7x7, plot_path, diff --git a/test_autolens/point/model/test_plotter_point.py b/test_autolens/point/model/test_plotter_point.py index 0bfd01f22..2b4a8d8f4 100644 --- a/test_autolens/point/model/test_plotter_point.py +++ b/test_autolens/point/model/test_plotter_point.py @@ -21,3 +21,17 @@ def test__fit_point(fit_point_dataset_x2_plane, plot_path, plot_patch): plotter.fit_point(fit=fit_point_dataset_x2_plane) assert str(plot_path / "fit.png") in plot_patch.paths + + +def test__fit_point__quick_update__writes_normal_fit_subplot( + fit_point_dataset_x2_plane, plot_path, plot_patch +): + if plot_path.exists(): + shutil.rmtree(plot_path) + + plotter = PlotterPoint(image_path=plot_path) + + plotter.fit_point(fit=fit_point_dataset_x2_plane, quick_update=True) + + assert str(plot_path / "fit.png") in plot_patch.paths + assert str(plot_path / "fit_quick.png") not in plot_patch.paths diff --git a/test_autolens/weak/model/__init__.py b/test_autolens/weak/model/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test_autolens/weak/model/test_plotter_weak.py b/test_autolens/weak/model/test_plotter_weak.py new file mode 100644 index 000000000..841849f9f --- /dev/null +++ b/test_autolens/weak/model/test_plotter_weak.py @@ -0,0 +1,53 @@ +from pathlib import Path + +import autoarray as aa +import autolens as al + +import pytest + +from autolens.weak.model.plotter import PlotterWeak + +directory = Path(__file__).resolve().parent + + +def _isothermal_tracer(einstein_radius=1.6, ell_comps=(0.0, 0.05)): + lens = al.Galaxy( + redshift=0.5, + mass=al.mp.Isothermal( + centre=(0.0, 0.0), + ell_comps=ell_comps, + einstein_radius=einstein_radius, + ), + ) + source = al.Galaxy(redshift=1.0) + return al.Tracer(galaxies=[lens, source]) + + +@pytest.fixture(name="fit_weak") +def make_fit_weak(): + grid = aa.Grid2DIrregular( + values=[(0.7, 0.5), (1.0, 1.0), (-0.3, 0.6), (-1.1, -0.8)] + ) + truth = _isothermal_tracer(einstein_radius=1.6) + dataset = al.SimulatorShearYX(noise_sigma=0.0, seed=0).via_tracer_from( + tracer=truth, grid=grid, name="test" + ) + dataset.noise_map = aa.ArrayIrregular(values=[0.3, 0.3, 0.3, 0.3]) + model = _isothermal_tracer(einstein_radius=1.5) + return al.FitWeak(dataset=dataset, tracer=model) + + +@pytest.fixture(name="plot_path") +def make_plot_path(): + return directory / "files" + + +def test__fit_weak__quick_update__writes_normal_fit_subplot( + fit_weak, plot_path, plot_patch +): + plotter = PlotterWeak(image_path=plot_path) + + plotter.fit_weak(fit=fit_weak, quick_update=True) + + assert str(plot_path / "subplot_fit_weak.png") in plot_patch.paths + assert str(plot_path / "fit_quick.png") not in plot_patch.paths