Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions autogalaxy/abstract_fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ def linear_light_profile_intensity_dict(
return linear_light_profile_intensity_dict

def galaxy_linear_obj_data_dict_from(
self, use_image: bool = False
self,
use_operated: bool = True,
) -> Dict[Galaxy, aa.Array2D]:
"""
Returns a dictionary mapping every galaxy containing a linear
Expand All @@ -129,16 +130,17 @@ def galaxy_linear_obj_data_dict_from(
This is used to create the overall `galaxy_model_image_dict`, which maps every galaxy to its
overall `model_data` (e.g. including the `model_data` of orindary light profiles too).

If `use_image=False`, the `reconstructed_data` of the inversion (e.g. an image for dataset data,
If `use_operated=False`, the `reconstructed_data` of the inversion (e.g. an image for dataset data,
visibilities for interferometer data) is input in the dictionary.

if `use_image=True`, the `reconstructed_image` of the inversion (e.g. the image for dataset data, the
if `use_operated=True`, the `reconstructed_operated_data` of the inversion (e.g. the image for dataset data, the
real-space image for interferometer data) is input in the dictionary.
Comment on lines +133 to 137
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring describing use_operated=True says the operated output is a “real-space image for interferometer data”, but in this refactor mapped_reconstructed_operated_data corresponds to the data after applying the operator (e.g. visibilities for interferometer, PSF-convolved image for imaging), as reflected by updated tests. Please update the docstring example for interferometer to avoid inverting the meaning of “operated”.

Copilot uses AI. Check for mistakes.

Parameters
----------
use_image
Whether to put the reconstructed data or images in the dictionary.
use_operated
Whether to use the operated (e.g PSF convolved) images of the linear objects in the dictionary, or
the unoperated images.

Returns
-------
Expand All @@ -156,13 +158,12 @@ def galaxy_linear_obj_data_dict_from(
except KeyError:
continue

if not use_image:
mapped_reconstructed = self.inversion.mapped_reconstructed_data_dict[
linear_obj
]

if use_operated:
mapped_reconstructed = (
self.inversion.mapped_reconstructed_operated_data_dict[linear_obj]
)
else:
mapped_reconstructed = self.inversion.mapped_reconstructed_image_dict[
mapped_reconstructed = self.inversion.mapped_reconstructed_data_dict[
linear_obj
]

Expand Down
5 changes: 3 additions & 2 deletions autogalaxy/config/visualize/plots.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# These can be disabled to save on hard-disk space but will lead to certain database functionality being disabled.

subplot_format: [png] # Output format of all subplots, can be png, pdf or both (e.g. [png, pdf])
fits_are_zoomed: true # If true, output .fits files are zoomed in on the center of the unmasked region image, saving hard-disk space.
fits_are_zoomed: false # If true, output .fits files are zoomed in on the center of the unmasked region image, saving hard-disk space.
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the default fits_are_zoomed setting from true to false, which can significantly increase .fits output sizes and affect downstream workflows expecting zoomed outputs. If this is not directly required for the new operated/unoperated galaxy image outputs, consider keeping the previous default (or clearly documenting the behavior change and rationale).

Copilot uses AI. Check for mistakes.

dataset: # Settings for plots of all datasets (e.g. ImagingPlotter, InterferometerPlotter).
subplot_dataset: true # Plot subplot containing all dataset quantities (e.g. the data, noise-map, etc.)?
Expand All @@ -23,7 +23,8 @@ fit: # Settings for plots of all fits (e.g
subplot_of_galaxies: false # Plot subplot of the model-image, subtracted image and other quantities of each galaxy?
subplot_galaxy_images: false # Plot subplot of the image of each galaxy in the model?
fits_fit: false # Output a .fits file containing the fit model data, residual map, normalized residual map and chi-squared?
fits_model_galaxy_images : false # Output a .fits file containing the model images of every galaxy?
fits_galaxy_images : false # Output a .fits file containing the images (e.g. without PSF convolution) of every galaxy?
fits_model_galaxy_images : false # 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).

Expand Down
31 changes: 29 additions & 2 deletions autogalaxy/imaging/fit_imaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,37 @@ def model_data(self) -> aa.Array2D:
"""

if self.perform_inversion:
return self.blurred_image + self.inversion.mapped_reconstructed_data
return (
self.blurred_image + self.inversion.mapped_reconstructed_operated_data
)

return self.blurred_image

@property
def galaxy_image_dict(self) -> Dict[Galaxy, np.ndarray]:
"""
A dictionary which associates every galaxy in the fit with its image before operation (e.g. no PSF convolution
or NUFFT performed).

This image is the image of the sum of:

- The images of all ordinary light profiles summed before any operation is performed on them.
- The images of all linear objects (e.g. linear light profiles / pixelizations), where the images are solved
for first via the inversion.

This dictionary is used to output to .fits file the galaxy images.
"""

galaxy_image_2d_dict = self.galaxies.galaxy_image_2d_dict_from(
grid=self.grids.lp,
)

galaxy_linear_obj_image_dict = self.galaxy_linear_obj_data_dict_from(
use_operated=False,
)

return {**galaxy_image_2d_dict, **galaxy_linear_obj_image_dict}

@property
def galaxy_model_image_dict(self) -> Dict[Galaxy, np.ndarray]:
"""
Expand All @@ -187,7 +214,7 @@ def galaxy_model_image_dict(self) -> Dict[Galaxy, np.ndarray]:
)

galaxy_linear_obj_image_dict = self.galaxy_linear_obj_data_dict_from(
use_image=True
use_operated=True,
)

return {**galaxy_blurred_image_2d_dict, **galaxy_linear_obj_image_dict}
Expand Down
16 changes: 16 additions & 0 deletions autogalaxy/imaging/model/plotter_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,22 @@ def fits_to_fits(

hdu_list.writeto(image_path / "fit.fits", overwrite=True)

if should_plot("fits_galaxy_images"):
number_plots = len(fit.galaxy_image_dict.keys()) + 1

image_list = [image.native_for_fits for image in fit.galaxy_image_dict.values()]

hdu_list = hdu_list_for_output_from(
values_list=[image_list[0].mask.astype("float")] + image_list,
ext_name_list=[
"mask",
]
+ [f"galaxy_{i}" for i in range(number_plots)],
header_dict=fit.mask.header_dict,
)
Comment on lines +63 to +75
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the new fits_galaxy_images output, ext_name_list is built using range(number_plots) where number_plots = len(fit.galaxy_image_dict) + 1, but values_list is only 1 + len(image_list). This makes the number of extension names differ from the number of HDUs being written, which can lead to incorrect labeling or an exception depending on hdu_list_for_output_from's validation. Build ext_name_list from the actual number of galaxy images (e.g. len(image_list) / len(fit.galaxy_image_dict)) so it matches values_list exactly.

Copilot uses AI. Check for mistakes.

hdu_list.writeto(image_path / "galaxy_images.fits", overwrite=True)

if should_plot("fits_model_galaxy_images"):
number_plots = len(fit.galaxy_model_image_dict.keys()) + 1

Expand Down
17 changes: 9 additions & 8 deletions autogalaxy/interferometer/fit_interferometer.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,15 @@ def model_data(self) -> aa.Visibilities:
"""

if self.perform_inversion:
return self.profile_visibilities + self.inversion.mapped_reconstructed_data
return (
self.profile_visibilities
+ self.inversion.mapped_reconstructed_operated_data
)

return self.profile_visibilities

@property
def galaxy_model_image_dict(self) -> Dict[Galaxy, np.ndarray]:
def galaxy_image_dict(self) -> Dict[Galaxy, np.ndarray]:
"""
A dictionary which associates every galaxy with its `image`.
Comment on lines 149 to 152
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FitInterferometer no longer defines galaxy_model_image_dict (it was renamed to galaxy_image_dict), but there are still call sites in the codebase that access galaxy_model_image_dict (e.g. autogalaxy/analysis/result.py uses self.max_log_likelihood_fit.galaxy_model_image_dict). This will raise AttributeError for interferometer results. Either update remaining call sites to the new name or keep a backwards-compatible alias property (potentially with a deprecation warning).

Copilot uses AI. Check for mistakes.

Expand All @@ -157,15 +160,13 @@ def galaxy_model_image_dict(self) -> Dict[Galaxy, np.ndarray]:
For modeling, this dictionary is used to set up the `adapt_images` that adapt certain pixelizations to the
data being fitted.
"""
galaxy_model_image_dict = self.galaxies.galaxy_image_2d_dict_from(
grid=self.grids.lp
)
galaxy_image_dict = self.galaxies.galaxy_image_2d_dict_from(grid=self.grids.lp)

galaxy_linear_obj_image_dict = self.galaxy_linear_obj_data_dict_from(
use_image=True
use_operated=False
)

return {**galaxy_model_image_dict, **galaxy_linear_obj_image_dict}
return {**galaxy_image_dict, **galaxy_linear_obj_image_dict}

@property
def galaxy_model_visibilities_dict(self) -> Dict[Galaxy, np.ndarray]:
Expand All @@ -186,7 +187,7 @@ def galaxy_model_visibilities_dict(self) -> Dict[Galaxy, np.ndarray]:
)

galaxy_linear_obj_data_dict = self.galaxy_linear_obj_data_dict_from(
use_image=False
use_operated=True
)

return {**galaxy_model_visibilities_dict, **galaxy_linear_obj_data_dict}
Expand Down
10 changes: 4 additions & 6 deletions autogalaxy/interferometer/model/plotter_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,18 @@ def fits_to_fits(
The fit to output to a .fits file.
"""

if should_plot("fits_model_galaxy_images"):
if should_plot("fits_galaxy_images"):

image_list = [
image.native_for_fits for image in fit.galaxy_model_image_dict.values()
]
image_list = [image.native_for_fits for image in fit.galaxy_image_dict.values()]

hdu_list = hdu_list_for_output_from(
values_list=[image_list[0].mask.astype("float")] + image_list,
ext_name_list=["mask"]
+ [f"galaxy_{i}" for i in range(len(fit.galaxy_model_image_dict.values()))],
+ [f"galaxy_{i}" for i in range(len(fit.galaxy_image_dict.values()))],
header_dict=fit.dataset.real_space_mask.header_dict,
)

hdu_list.writeto(image_path / "model_galaxy_images.fits", overwrite=True)
hdu_list.writeto(image_path / "galaxy_images.fits", overwrite=True)

if should_plot("fits_dirty_images"):

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def subplot_fit_real_space(self):
mapper_index = 0

self.inversion_plotter.figures_2d_of_pixelization(
pixelization_index=mapper_index, reconstructed_image=True
pixelization_index=mapper_index, reconstructed_operated_data=True
)
self.inversion_plotter.figures_2d_of_pixelization(
pixelization_index=mapper_index, reconstruction=True
Expand Down
4 changes: 3 additions & 1 deletion test_autogalaxy/config/visualize.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -331,13 +331,15 @@ mat_wrap_2d:
marker: .
s: 17
plots:
fits_are_zoomed: true
dataset:
subplot_dataset: true
fit:
subplot_fit: true
subplot_of_galaxies: false
fits_fit: true # Output a .fits file containing the fit model data, residual map, normalized residual map and chi-squared?
fits_model_galaxy_images : true # Output a .fits file containing the model images of every galaxy?
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: {}
fit_interferometer:
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?
Expand Down
4 changes: 2 additions & 2 deletions test_autogalaxy/galaxy/test_to_inversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ def test__inversion_imaging_from(grid_2d_7x7, masked_imaging_7x7):

inversion = to_inversion.inversion

assert inversion.mapped_reconstructed_image == pytest.approx(
assert inversion.mapped_reconstructed_operated_data == pytest.approx(
masked_imaging_7x7.data, 1.0e-2
)

Expand Down Expand Up @@ -215,7 +215,7 @@ def test__inversion_interferometer_from(grid_2d_7x7, interferometer_7):

inversion = to_inversion.inversion

assert inversion.mapped_reconstructed_data.real == pytest.approx(
assert inversion.mapped_reconstructed_operated_data.real == pytest.approx(
interferometer_7.data.real, 1.0e-2
)

Expand Down
77 changes: 72 additions & 5 deletions test_autogalaxy/imaging/test_fit_imaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,73 @@ def test__fit__model_dataset__grid_offset__handles_special_behaviour(
assert fit.figure_of_merit == pytest.approx(-22.914118686169, 1.0e-4)


def test__galaxy_image_dict(masked_imaging_7x7):
# Normal Light Profiles Only

g0 = ag.Galaxy(redshift=0.5, bulge=ag.lp.Sersic(intensity=1.0))
g1 = ag.Galaxy(redshift=0.5, bulge=ag.lp.Sersic(intensity=2.0))
g2 = ag.Galaxy(
redshift=0.5,
light_profile_0=ag.lp.Sersic(intensity=1.0),
light_profile_1=ag.lp.Sersic(intensity=2.0),
)
g3 = ag.Galaxy(redshift=0.5)

fit = ag.FitImaging(dataset=masked_imaging_7x7, galaxies=[g0, g1, g2, g3])

g0_image_2d = g0.image_2d_from(
grid=masked_imaging_7x7.grids.lp,
)

g1_image_2d = g1.image_2d_from(
grid=masked_imaging_7x7.grids.lp,
)

assert fit.galaxy_image_dict[g0] == pytest.approx(g0_image_2d.array, 1.0e-4)
assert fit.galaxy_image_dict[g1] == pytest.approx(g1_image_2d.array, 1.0e-4)
assert fit.galaxy_image_dict[g2] == pytest.approx(
g0_image_2d.array + g1_image_2d.array, 1.0e-4
)
assert (fit.galaxy_image_dict[g3].slim == np.zeros(9)).all()

# Linear Light PRofiles + Pixelization + Regularizaiton

g0 = ag.Galaxy(redshift=0.5, bulge=ag.lp.Sersic(intensity=1.0, centre=(0.05, 0.05)))
g1_linear = ag.Galaxy(redshift=0.5, bulge=ag.lp_linear.Sersic(centre=(0.05, 0.05)))

pixelization = ag.Pixelization(
mesh=ag.mesh.RectangularUniform(shape=(3, 3)),
regularization=ag.reg.Constant(coefficient=1.0),
)

galaxy_pix_0 = ag.Galaxy(redshift=0.5, pixelization=pixelization)
galaxy_pix_1 = ag.Galaxy(redshift=0.5, pixelization=pixelization)

masked_imaging_7x7.data[0] = 3.0

fit = ag.FitImaging(
dataset=masked_imaging_7x7,
galaxies=[g0, g1_linear, g3, galaxy_pix_0, galaxy_pix_1],
)

assert (fit.galaxy_image_dict[g3] == np.zeros(9)).all()

assert fit.galaxy_image_dict[g0][4] == pytest.approx(23.944378406, 1.0e-4)
assert fit.galaxy_image_dict[g1_linear][4] == pytest.approx(-28.1424200, 1.0e-4)
assert fit.galaxy_image_dict[galaxy_pix_0][4] == pytest.approx(1.107972830, 1.0e-4)
assert fit.galaxy_image_dict[galaxy_pix_1][4] == pytest.approx(1.1079728213, 1.0e-4)

mapped_reconstructed_data = (
fit.galaxy_image_dict[g1_linear]
+ fit.galaxy_image_dict[galaxy_pix_0]
+ fit.galaxy_image_dict[galaxy_pix_1]
)

assert mapped_reconstructed_data == pytest.approx(
fit.inversion.mapped_reconstructed_data.array, 1.0e-4
)


def test__galaxy_model_image_dict(masked_imaging_7x7):
# Normal Light Profiles Only

Expand Down Expand Up @@ -309,7 +376,7 @@ def test__galaxy_model_image_dict(masked_imaging_7x7):

assert fit.galaxy_model_image_dict[g1][4] == pytest.approx(1.25795063, 1.0e-4)
assert fit.galaxy_model_image_dict[g1].native.array == pytest.approx(
fit.inversion.mapped_reconstructed_image.native.array, 1.0e-4
fit.inversion.mapped_reconstructed_operated_data.native.array, 1.0e-4
)

assert fit.model_data.native.array == pytest.approx(
Expand Down Expand Up @@ -349,19 +416,19 @@ def test__galaxy_model_image_dict(masked_imaging_7x7):
1.10780906, 1.0e-4
)

mapped_reconstructed_image = (
mapped_reconstructed_operated_data = (
fit.galaxy_model_image_dict[g1_linear]
+ fit.galaxy_model_image_dict[galaxy_pix_0]
+ fit.galaxy_model_image_dict[galaxy_pix_1]
)

assert mapped_reconstructed_image == pytest.approx(
fit.inversion.mapped_reconstructed_image.array, 1.0e-4
assert mapped_reconstructed_operated_data == pytest.approx(
fit.inversion.mapped_reconstructed_operated_data.array, 1.0e-4
)

assert fit.model_data == pytest.approx(
fit.galaxy_model_image_dict[g0].array
+ fit.inversion.mapped_reconstructed_image.array,
+ fit.inversion.mapped_reconstructed_operated_data.array,
1.0e-4,
)

Expand Down
Loading
Loading