diff --git a/autoarray/config/visualize/plots.yaml b/autoarray/config/visualize/plots.yaml index da0e330ef..aa2664506 100644 --- a/autoarray/config/visualize/plots.yaml +++ b/autoarray/config/visualize/plots.yaml @@ -5,19 +5,11 @@ 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.)? - data: false # Plot the individual data of every dataset? - noise_map: false # Plot the individual noise-map of every dataset? - signal_to_noise_map: false # Plot the individual signal-to-noise-map of every dataset? - over_sample_size_lp: false # Plot the over-sampling size, used to evaluate light profiles, of every dataset? - over_sample_size_pixelization: false # Plot the over-sampling size, used to evaluate pixelizations, of every dataset? imaging: # Settings for plots of imaging datasets (e.g. ImagingPlotter) psf: false fit: # Settings for plots of all fits (e.g. FitImagingPlotter, FitInterferometerPlotter). 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.)? - all_at_end_png: true # Plot all individual plots listed below as .png (even if False)? - all_at_end_fits: true # Plot all individual plots listed below as .fits (even if False)? - all_at_end_pdf: false # Plot all individual plots listed below as publication-quality .pdf (even if False)? data: false # Plot individual plots of the data? noise_map: false # Plot individual plots of the noise-map? signal_to_noise_map: false # Plot individual plots of the signal-to-noise-map? @@ -30,9 +22,6 @@ fit_imaging: {} # Settings for plots of fits to imagi inversion: # Settings for plots of inversions (e.g. InversionPlotter). subplot_inversion: true # Plot subplot of all quantities in each inversion (e.g. reconstrucuted image, reconstruction)? subplot_mappings: true # Plot subplot of the image-to-source pixels mappings of each pixelization? - all_at_end_png: true # Plot all individual plots listed below as .png (even if False)? - all_at_end_fits: true # Plot all individual plots listed below as .fits (even if False)? - all_at_end_pdf: false # Plot all individual plots listed below as publication-quality .pdf (even if False)? data_subtracted: false # Plot individual plots of the data with the other inversion linear objects subtracted? reconstruction_noise_map: false # Plot image of the noise of every mesh-pixel reconstructed value? sub_pixels_per_image_pixels: false # Plot the number of sub pixels per masked data pixels? @@ -41,22 +30,6 @@ inversion: # Settings for plots of inversions (e reconstructed_image: false # Plot image of the reconstructed data (e.g. in the image-plane)? reconstruction: false # Plot the reconstructed inversion (e.g. the pixelization's mesh in the source-plane)? regularization_weights: false # Plot the effective regularization weight of every inversion mesh pixel? -interferometer: # Settings for plots of interferometer datasets (e.g. InterferometerPlotter). - amplitudes_vs_uv_distances: false - phases_vs_uv_distances: false - uv_wavelengths: false - dirty_image: false - dirty_noise_map: false - dirty_signal_to_noise_map: false fit_interferometer: # Settings for plots of fits to interferometer datasets (e.g. FitInterferometerPlotter). 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? - amplitudes_vs_uv_distances: false - phases_vs_uv_distances: false - uv_wavelengths: false - dirty_image: false - dirty_noise_map: false - dirty_signal_to_noise_map: false - dirty_residual_map: false - dirty_normalized_residual_map: false - dirty_chi_squared_map: false \ No newline at end of file + subplot_fit_real_space: false # Plot subplot of the real-space images of all interferometer datasets? \ No newline at end of file diff --git a/autoarray/inversion/inversion/mapper_valued.py b/autoarray/inversion/inversion/mapper_valued.py index f12bec261..175046b9d 100644 --- a/autoarray/inversion/inversion/mapper_valued.py +++ b/autoarray/inversion/inversion/mapper_valued.py @@ -259,7 +259,7 @@ def magnification_via_mesh_from( def magnification_via_interpolation_from( self, - shape_native: Tuple[int, int] = (401, 401), + shape_native: Tuple[int, int] = (201, 201), extent: Optional[Tuple[float, float, float, float]] = None, ) -> float: """ diff --git a/autoarray/operators/transformer.py b/autoarray/operators/transformer.py index 89e48ecf2..acbe6bb73 100644 --- a/autoarray/operators/transformer.py +++ b/autoarray/operators/transformer.py @@ -47,7 +47,7 @@ def pylops_exception(): "\n--------------------\n" "You are attempting to perform interferometer analysis.\n\n" "However, the optional library PyLops (https://github.com/PyLops/pylops) is not installed.\n\n" - "Install it via the command `pip install pylops==1.18.3`.\n\n" + "Install it via the command `pip install pylops==2.3.1`.\n\n" "----------------------" ) diff --git a/autoarray/plot/multi_plotters.py b/autoarray/plot/multi_plotters.py index 01ed8364f..5c2c5071e 100644 --- a/autoarray/plot/multi_plotters.py +++ b/autoarray/plot/multi_plotters.py @@ -1,3 +1,5 @@ +import os +from pathlib import Path from typing import List, Optional, Tuple from autoarray.plot.wrap.base.ticks import YTicks @@ -104,29 +106,6 @@ def plot_via_func(self, plotter, figure_name: str, func_name: str, kwargs): else: func(**{**{figure_name: True}, **kwargs}) - def output_subplot(self, filename_suffix: str = ""): - """ - Outplot the subplot to a file after all figures have been plotted on the subplot. - - The multi-plotter requires its own output function to ensure that the subplot is output to a file, which - this provides. - - Parameters - ---------- - filename_suffix - The suffix of the filename that the subplot is output to. - """ - - if self.plotter_list[0].mat_plot_1d is not None: - self.plotter_list[0].mat_plot_1d.output.subplot_to_figure( - auto_filename=f"subplot_{filename_suffix}" - ) - if self.plotter_list[0].mat_plot_2d is not None: - self.plotter_list[0].mat_plot_2d.output.subplot_to_figure( - auto_filename=f"subplot_{filename_suffix}" - ) - self.plotter_list[0].close_subplot_figure() - def subplot_of_figure( self, func_name: str, figure_name: str, filename_suffix: str = "", **kwargs ): @@ -266,6 +245,99 @@ def subplot_of_multi_yx_1d(self, filename_suffix="", **kwargs): ) self.plotter_list[0].plotter_list[0].close_subplot_figure() + def output_subplot(self, filename_suffix: str = ""): + """ + Outplot the subplot to a file after all figures have been plotted on the subplot. + + The multi-plotter requires its own output function to ensure that the subplot is output to a file, which + this provides. + + Parameters + ---------- + filename_suffix + The suffix of the filename that the subplot is output to. + """ + + plotter = self.plotter_list[0] + + if plotter.mat_plot_1d is not None: + plotter.mat_plot_1d.output.subplot_to_figure( + auto_filename=f"subplot_{filename_suffix}" + ) + if plotter.mat_plot_2d is not None: + plotter.mat_plot_2d.output.subplot_to_figure( + auto_filename=f"subplot_{filename_suffix}" + ) + plotter.close_subplot_figure() + + def output_to_fits( + self, + func_name_list: List[str], + figure_name_list: List[str], + filename: str, + tag_list: Optional[List[str]] = None, + remove_fits_first: bool = False, + **kwargs, + ): + """ + Outputs a list of figures of the plotter objects in the `plotter_list` to a single .fits file. + + This function takes as input lists of function names and figure names and then calls them via + the `plotter_list` with an interface that outputs each to a .fits file. + + For example, if you have multiple `ImagingPlotter` objects and want to output the `data` and `noise_map` of + each to a single .fits files, you would input: + + - `func_name_list=['figures_2d', 'figures_2d']` and + - `figure_name_list=['data', 'noise_map']`. + + The implementation of this code is hacky, with it using a specific interface in the `Output` object + which sets the format to `fits_multi` to call a function which outputs the .fits files. A major visualuzation + refactor is required to make this more elegant. + + Parameters + ---------- + func_name_list + The list of function names that are called to plot the figures on the subplot. + figure_name_list + The list of figure names that are plotted on the subplot. + filenane + The filename that the .fits file is output to. + tag_list + The list of tags that are used to set the `EXTNAME` of each hdu of the .fits file. + remove_fits_first + If the .fits file already exists, it is removed before the new .fits file is output, else it is updated + with the figure going into the next hdu. + kwargs + Any additional keyword arguments that are passed to the function that plots the figure on the subplot. + """ + + output_path = self.plotter_list[0].mat_plot_2d.output.output_path_from( + format="fits_multi" + ) + output_fits_file = Path(output_path)/ f"{filename}.fits" + + if remove_fits_first: + output_fits_file.unlink(missing_ok=True) + + for i, plotter in enumerate(self.plotter_list): + plotter.mat_plot_2d.output._format = "fits_multi" + + plotter.set_filename(filename=f"{filename}") + + for j, (func_name, figure_name) in enumerate( + zip(func_name_list, figure_name_list) + ): + if tag_list is not None: + plotter.mat_plot_2d.output._tag_fits_multi = tag_list[j] + + self.plot_via_func( + plotter=plotter, + figure_name=figure_name, + func_name=func_name, + kwargs=kwargs, + ) + class MultiYX1DPlotter: def __init__( diff --git a/autoarray/plot/wrap/base/output.py b/autoarray/plot/wrap/base/output.py index 6646e8c21..945f91abd 100644 --- a/autoarray/plot/wrap/base/output.py +++ b/autoarray/plot/wrap/base/output.py @@ -61,6 +61,7 @@ def __init__( self.format_folder = format_folder self.bypass = bypass self.bbox_inches = bbox_inches + self._tag_fits_multi = None self.kwargs = kwargs @@ -105,6 +106,7 @@ def savefig(self, filename: str, output_path: str, format: str): plt.savefig( path.join(output_path, f"{filename}.{format}"), bbox_inches=self.bbox_inches, + pad_inches=0, ) except ValueError as e: logger.info( @@ -151,6 +153,17 @@ def to_figure( file_path=path.join(output_path, f"{filename}.fits"), overwrite=True, ) + elif format == "fits_multi": + if structure is not None: + from autoarray.structures.arrays.array_2d_util import ( + update_fits_file, + ) + + update_fits_file( + arr=structure.native, + file_path=path.join(output_path, f"{filename}.fits"), + tag=self._tag_fits_multi, + ) def subplot_to_figure(self, auto_filename: Optional[str] = None): """ diff --git a/autoarray/plot/wrap/two_d/voronoi_drawer.py b/autoarray/plot/wrap/two_d/voronoi_drawer.py index 9e189413b..aec6661cc 100644 --- a/autoarray/plot/wrap/two_d/voronoi_drawer.py +++ b/autoarray/plot/wrap/two_d/voronoi_drawer.py @@ -81,7 +81,7 @@ def draw_voronoi_pixels( cmap = plt.get_cmap(cmap.cmap) - if colorbar is not None: + if colorbar is not None and colorbar is not False: cb = colorbar.set_with_color_values( units=units, norm=norm, diff --git a/autoarray/structures/arrays/array_2d_util.py b/autoarray/structures/arrays/array_2d_util.py index 72cb8437e..1b9b902fc 100644 --- a/autoarray/structures/arrays/array_2d_util.py +++ b/autoarray/structures/arrays/array_2d_util.py @@ -862,3 +862,61 @@ def header_obj_from(file_path: Union[Path, str], hdu: int) -> Dict: hdu_list = fits.open(file_path) return hdu_list[hdu].header + + +def update_fits_file( + arr: np.ndarray, + file_path: str, + tag: Optional[str] = None, + header: Optional[fits.Header] = None, +): + """ + Update a .fits file with a new array. + + This function is used by the `fits_multi` output interface so that a single .fits file with groups of data + in hdu's can be created. + + It may receive a `tag` which is used to set the `EXTNAME` of the HDU in the .fits file and therefore is the name + of the hdu seen by the user when they open it with DS9 or other .fits software. + + A header may also be provided, which by default has the pixel scales of the array added to it. + + Parameters + ---------- + arr + The array that is written to the .fits file. + file_path + The full path of the file that is output, including the file name and ``.fits`` extension. + tag + The `EXTNAME` of the HDU in the .fits file. + header + The header of the .fits file that the array is written to, which if blank will still contain the pixel scales + of the array. + """ + + if header is None: + header = fits.Header() + + try: + y, x = map(str, arr.pixel_scales) + header["PIXSCAY"] = y + header["PIXSCAX"] = x + except AttributeError: + pass + + if conf.instance["general"]["fits"]["flip_for_ds9"]: + arr = np.flipud(arr) + + if os.path.exists(file_path): + with fits.open(file_path, mode="update") as hdul: + hdul.append(fits.ImageHDU(arr, header)) + if tag is not None: + hdul[-1].header["EXTNAME"] = tag.upper() + hdul.flush() + + else: + hdu = fits.PrimaryHDU(arr, header) + if tag is not None: + hdu.header["EXTNAME"] = tag.upper() + hdul = fits.HDUList([hdu]) + hdul.writeto(file_path, overwrite=True)