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
2 changes: 1 addition & 1 deletion autolens/imaging/model/visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def visualize(
try:
fit.inversion.reconstruction
except exc.InversionException:
logger(
logger.warning(
ag.exc.invalid_linear_algebra_for_visualization_message()
)
return
Expand Down
61 changes: 61 additions & 0 deletions autolens/interferometer/fit_interferometer.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,67 @@ def galaxy_image_dict(self) -> Dict[ag.Galaxy, np.ndarray]:

return {**galaxy_image_dict, **galaxy_linear_obj_image_dict}

@property
def model_image_natural(self) -> aa.Array2D:
"""
The real-space (image-plane) model image `m` of the fit, on the dataset's `real_space_mask`: the lensed
image of every ordinary (non-linear) light profile (`profile_image`) plus, when the fit has an
inversion, the solved linear objects' reconstruction mapped to the image plane
(`inversion.mapped_reconstructed_data`, linear light profiles and pixelized sources) -- the real-space
image whose visibilities are `model_data`.

It is built from these two terms rather than from `galaxy_image_dict`, whose entry for a galaxy with
both ordinary and linear light holds only the linear reconstruction.

It needs neither visibilities nor a transformer, so it is available on an array-free dataset (built by
`Interferometer.from_stream` / `from_sparse_terms`), where it is the image the natural-weighted dirty
model image `dirty_model_image_natural` is formed from. (There `profile_image` is all zeros, because a
fit with ordinary light on an array-free dataset raises before it gets here.)
"""
image = np.asarray(
getattr(self.profile_image, "array", self.profile_image), dtype=np.float64
)

if self.inversion is not None:
reconstruction = self.inversion.mapped_reconstructed_data
image = image + np.asarray(
getattr(reconstruction, "array", reconstruction), dtype=np.float64
)

return aa.Array2D(
values=image,
mask=self.dataset.real_space_mask,
)

@property
def dirty_model_image_natural(self) -> aa.Array2D:
"""
The naturally weighted, normalised dirty image of the model visibilities, `W~ m / sum(w)`, formed from
`model_image_natural` with the dataset's `sparse_operator` (see
`autoarray.fit.fit_interferometer.dirty_model_image_natural_from`).

It is the model counterpart of the dataset's `dirty_image_natural` and needs no visibilities, so it is
how a fit on an array-free dataset is visualized. It is available on any dataset carrying a
`sparse_operator` (array-free, or in-memory after `apply_sparse_operator()`); otherwise it raises an
`aa.exc.DatasetException`.
"""
return aa.fit.fit_interferometer.dirty_model_image_natural_from(
dataset=self.dataset, image=self.model_image_natural
)

@property
def dirty_residual_map_natural(self) -> aa.Array2D:
"""
The naturally weighted dirty residual map, `dirty_image_natural - dirty_model_image_natural`, which is
`Re(F^H W (d - F m)) / sum(w)`: the natural dirty image of the visibility residuals, computed without
them.
"""
return aa.Array2D(
values=np.asarray(self.dataset.dirty_image_natural.array)
- np.asarray(self.dirty_model_image_natural.array),
mask=self.dataset.real_space_mask,
)

@property
def galaxy_signal_to_noise_map_dict(self) -> Dict[ag.Galaxy, np.ndarray]:
"""
Expand Down
13 changes: 9 additions & 4 deletions autolens/interferometer/model/visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,14 @@ def visualize_before_fit(

positions = ag.Grid2DIrregular(positions_list)

plotter.image_with_positions(
image=analysis.dataset.dirty_image, positions=positions
)
# An array-free dataset (from_stream / from_sparse_terms) has no visibilities to
# form the unweighted dirty image from, so its natural-weighted one is shown.
if analysis.dataset.is_array_free:
image = analysis.dataset.dirty_image_natural
else:
image = analysis.dataset.dirty_image

plotter.image_with_positions(image=image, positions=positions)

if analysis.adapt_images is not None:
plotter.adapt_images(adapt_images=analysis.adapt_images)
Expand Down Expand Up @@ -116,7 +121,7 @@ def visualize(
source_plane_line_colors=sp_colors,
)
except exc.InversionException:
logger(ag.exc.invalid_linear_algebra_for_visualization_message())
logger.warning(ag.exc.invalid_linear_algebra_for_visualization_message())
return

if quick_update:
Expand Down
122 changes: 106 additions & 16 deletions autolens/interferometer/plot/fit_interferometer_plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ def subplot_fit(
* Dirty chi-squared map
* Source plane image (full extent)

On an array-free dataset (``fit.dataset.is_array_free``) there are no visibilities, so a
2 × 3 grid of the natural-weighted dirty image, dirty model image and dirty residual map
plus the zoomed and unzoomed source plane is written to the same filename instead.

Parameters
----------
fit : FitInterferometer
Expand All @@ -164,6 +168,37 @@ def subplot_fit(
)

_pf = (lambda t: f"{title_prefix.rstrip()} {t}") if title_prefix else (lambda t: t)

if fit.dataset.is_array_free:
# An array-free dataset (from_stream / from_sparse_terms) has no visibilities, so the
# visibility-space and unweighted dirty panels are replaced by the natural-weighted
# dirty image / model / residual (from the sparse terms) and the source plane.
fig, axes = subplots(2, 3, figsize=conf_subplot_figsize(2, 3))
axes_flat = list(axes.flatten())

plot_array(array=fit.dataset.dirty_image_natural, ax=axes_flat[0],
title=_pf("Dirty Image (Natural)"), colormap=colormap)
plot_array(array=fit.dirty_model_image_natural, ax=axes_flat[1],
title=_pf("Dirty Model Image (Natural)"), colormap=colormap,
lines=image_plane_lines, line_colors=image_plane_line_colors)
plot_array(array=fit.dirty_residual_map_natural, ax=axes_flat[2],
title=_pf("Dirty Residual Map (Natural)"), colormap=colormap)
_plot_source_plane(fit, axes_flat[3], final_plane_index,
zoom_to_brightest=True, colormap=colormap,
title=_pf("Source Plane (Zoomed)"),
lines=source_plane_lines,
line_colors=source_plane_line_colors)
_plot_source_plane(fit, axes_flat[4], final_plane_index,
zoom_to_brightest=False, colormap=colormap,
title=_pf("Source Plane (No Zoom)"),
lines=source_plane_lines,
line_colors=source_plane_line_colors)
axes_flat[5].axis("off")

tight_layout()
save_figure(fig, path=output_path, filename="fit", format=output_format)
return

fig, axes = subplots(3, 4, figsize=conf_subplot_figsize(3, 4))
axes_flat = list(axes.flatten())

Expand Down Expand Up @@ -265,6 +300,9 @@ def subplot_fit_dirty_images(
Dirty Image | Dirty Signal-To-Noise Map | Dirty Model Image (critical curves)
Dirty Residual Map | Dirty Norm Residual Map | Dirty Chi-Squared Map

On an array-free dataset (``fit.dataset.is_array_free``) a 1 × 3 subplot of the
natural-weighted dirty image, dirty model image and dirty residual map is written instead.

Parameters
----------
fit : FitInterferometer
Expand All @@ -286,6 +324,25 @@ def subplot_fit_dirty_images(
)

_pf = (lambda t: f"{title_prefix.rstrip()} {t}") if title_prefix else (lambda t: t)

if fit.dataset.is_array_free:
fig, axes = subplots(1, 3, figsize=conf_subplot_figsize(1, 3))
axes_flat = list(axes.flatten())

plot_array(array=fit.dataset.dirty_image_natural, ax=axes_flat[0],
title=_pf("Dirty Image (Natural)"), colormap=colormap,
use_log10=use_log10)
plot_array(array=fit.dirty_model_image_natural, ax=axes_flat[1],
title=_pf("Dirty Model Image (Natural)"), colormap=colormap,
use_log10=use_log10, lines=image_plane_lines,
line_colors=image_plane_line_colors)
plot_array(array=fit.dirty_residual_map_natural, ax=axes_flat[2],
title=_pf("Dirty Residual Map (Natural)"), colormap=colormap)

tight_layout()
save_figure(fig, path=output_path, filename="fit_dirty_images", format=output_format)
return

fig, axes = subplots(2, 3, figsize=conf_subplot_figsize(2, 3))
axes_flat = list(axes.flatten())

Expand Down Expand Up @@ -329,6 +386,9 @@ def subplot_fit_interferometer_combined(
different panel choice because interferometer fits are most informatively
visualised in dirty-image space.

A fit on an array-free dataset (``fit.dataset.is_array_free``) has no visibilities, so its row
shows the natural-weighted dirty image, dirty model image, source plane and dirty residual map.

Parameters
----------
fit_list : list of FitInterferometer
Expand Down Expand Up @@ -362,17 +422,23 @@ def subplot_fit_interferometer_combined(
tracer, cc_grid
)

array_free = fit.dataset.is_array_free

plot_array(
array=fit.dirty_image,
array=fit.dataset.dirty_image_natural if array_free else fit.dirty_image,
ax=row_axes[0],
title=_pf(f"Dirty Image (ch {row})"),
title=_pf(
f"Dirty Image (Natural) (ch {row})"
if array_free
else f"Dirty Image (ch {row})"
),
colormap=colormap,
)

plot_array(
array=fit.dirty_model_image,
array=fit.dirty_model_image_natural if array_free else fit.dirty_model_image,
ax=row_axes[1],
title=_pf("Dirty Model Image"),
title=_pf("Dirty Model Image (Natural)" if array_free else "Dirty Model Image"),
colormap=colormap,
lines=ip_lines,
line_colors=ip_colors,
Expand All @@ -391,13 +457,21 @@ def subplot_fit_interferometer_combined(
except Exception:
row_axes[2].axis("off")

plot_array(
array=fit.dirty_normalized_residual_map,
ax=row_axes[3],
title=_pf("Dirty Norm Residual"),
colormap=colormap,
cb_unit=r"$\sigma$",
)
if array_free:
plot_array(
array=fit.dirty_residual_map_natural,
ax=row_axes[3],
title=_pf("Dirty Residual Map (Natural)"),
colormap=colormap,
)
else:
plot_array(
array=fit.dirty_normalized_residual_map,
ax=row_axes[3],
title=_pf("Dirty Norm Residual"),
colormap=colormap,
cb_unit=r"$\sigma$",
)

tight_layout()
save_figure(fig, path=output_path, filename="fit_combined", format=output_format)
Expand Down Expand Up @@ -456,8 +530,18 @@ def subplot_fit_real_space(
lines=source_plane_lines, line_colors=source_plane_line_colors)
else:
# Pixelized source: dirty model image + source reconstruction
plot_array(array=fit.dirty_model_image, ax=axes_flat[0],
title=_pf("Reconstructed Image"), colormap=colormap)
# An array-free dataset has no transformer: its dirty model image is the
# natural-weighted one formed from the sparse terms.
plot_array(
array=(
fit.dirty_model_image_natural
if fit.dataset.is_array_free
else fit.dirty_model_image
),
ax=axes_flat[0],
title=_pf("Reconstructed Image"),
colormap=colormap,
)
_plot_source_plane(fit, axes_flat[1], final_plane_index,
zoom_to_brightest=True, colormap=colormap,
title=_pf("Source Reconstruction"),
Expand Down Expand Up @@ -532,9 +616,15 @@ def subplot_tracer_from_fit(
axes_flat = list(axes.flatten())

# Panel 0: Dirty Model Image
plot_array(array=fit.dirty_model_image, ax=axes_flat[0], title=_pf("Dirty Model Image"),
lines=image_plane_lines, line_colors=image_plane_line_colors,
colormap=colormap)
if fit.dataset.is_array_free:
plot_array(array=fit.dirty_model_image_natural, ax=axes_flat[0],
title=_pf("Dirty Model Image (Natural)"),
lines=image_plane_lines, line_colors=image_plane_line_colors,
colormap=colormap)
else:
plot_array(array=fit.dirty_model_image, ax=axes_flat[0], title=_pf("Dirty Model Image"),
lines=image_plane_lines, line_colors=image_plane_line_colors,
colormap=colormap)

# Panel 1: Lensed source image (image-plane projection).
# Use galaxy_image_dict so that pixelized (inversion) sources are included.
Expand Down
Binary file not shown.
Loading
Loading