From f563349d188b32a6019619f8bd254517c3107f77 Mon Sep 17 00:00:00 2001 From: James Nightingale Date: Thu, 20 Mar 2025 10:46:32 +0000 Subject: [PATCH 01/13] hdu_for_output updated to hdu_for_output_from --- autoarray/structures/arrays/array_2d_util.py | 23 +++++++++++++++----- autoarray/structures/arrays/uniform_2d.py | 8 ++++--- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/autoarray/structures/arrays/array_2d_util.py b/autoarray/structures/arrays/array_2d_util.py index 1b9b902fc..230f366e9 100644 --- a/autoarray/structures/arrays/array_2d_util.py +++ b/autoarray/structures/arrays/array_2d_util.py @@ -711,8 +711,8 @@ def array_2d_native_complex_via_indexes_from( def hdu_for_output_from( - array_2d: np.ndarray, header_dict: Optional[dict] = None -) -> fits.PrimaryHDU: + array_2d: np.ndarray, header_dict: Optional[dict] = None, ext_name : Optional[str] = None, return_as_primary : bool = False +) -> Union[fits.PrimaryHDU, fits.ImageHDU]: """ Returns the HDU which can be used to output an array to a .fits file. @@ -720,12 +720,20 @@ def hdu_for_output_from( config files. This is for Astronomy projects so that structures appear the same orientation as ``.fits`` files loaded in DS9. + The output .fits files may contain multiple HDUs comprising different images. Conventionally, the first array, + the `PrimaryHDU`, contains the 2D mask applied to the data and the remaining HDUs contain the data itself. + Parameters ---------- array_2d The 2D array that is written to fits. header_dict A dictionary of values that are written to the header of the .fits file. + ext_name + The name of the extension in the fits file, which displays in the header of the fits file and is visible + for example when the .fits is loaded in DS9. + return_as_primary + Whether the HDU is returned as a PrimaryHDU or ImageHDU. Returns ------- @@ -735,10 +743,12 @@ def hdu_for_output_from( Examples -------- array_2d = np.ones((5,5)) - hdu_for_output_from(array_2d=array_2d, file_path='/path/to/file/filename.fits', overwrite=True) + hdu_for_output_from(array_2d=array_2d, header_dict={"Example": 0.5}, ext_name="data", return_as_primary=True) """ header = fits.Header() + header["EXTNAME"] = ext_name.upper() + if header_dict is not None: for key, value in header_dict.items(): header.append((key, value, [""])) @@ -746,8 +756,11 @@ def hdu_for_output_from( flip_for_ds9 = conf.instance["general"]["fits"]["flip_for_ds9"] if flip_for_ds9: - return fits.PrimaryHDU(np.flipud(array_2d), header=header) - return fits.PrimaryHDU(array_2d, header=header) + array_2d = np.flipud(array_2d) + + if return_as_primary: + return fits.PrimaryHDU(array_2d, header=header) + return fits.ImageHDU(array_2d, header=header) def numpy_array_2d_to_fits( diff --git a/autoarray/structures/arrays/uniform_2d.py b/autoarray/structures/arrays/uniform_2d.py index 823558d80..cbc37a512 100644 --- a/autoarray/structures/arrays/uniform_2d.py +++ b/autoarray/structures/arrays/uniform_2d.py @@ -613,8 +613,7 @@ def trimmed_after_convolution_from( store_native=self.store_native, ) - @property - def hdu_for_output(self) -> fits.PrimaryHDU: + def hdu_for_output_from(self, ext_name : Optional[str] = None, return_as_primary : bool = False) -> Union[fits.PrimaryHDU, fits.ImageHDU]: """ The array as an HDU object, which can be output to a .fits file. @@ -628,7 +627,10 @@ def hdu_for_output(self) -> fits.PrimaryHDU: The HDU containing the data and its header which can then be written to .fits. """ return array_2d_util.hdu_for_output_from( - array_2d=np.array(self.native), header_dict=self.pixel_scale_header + array_2d=np.array(self.native), + header_dict=self.pixel_scale_header, + ext_name=ext_name, + return_as_primary=return_as_primary, ) def output_to_fits(self, file_path: Union[Path, str], overwrite: bool = False): From 7dbe9e52a1aea8cb5c6a47e43591bc0ab98e9d8b Mon Sep 17 00:00:00 2001 From: James Nightingale Date: Thu, 20 Mar 2025 10:54:38 +0000 Subject: [PATCH 02/13] moved hdu_for_output up to autoconf --- autoarray/dataset/interferometer/dataset.py | 12 +- .../inversion_interferometer_util.py | 365 +++++++++--------- .../inversion/inversion/inversion_util.py | 3 - autoarray/structures/abstract_structure.py | 4 - autoarray/structures/arrays/array_1d_util.py | 34 -- autoarray/structures/arrays/array_2d_util.py | 52 --- autoarray/structures/arrays/uniform_2d.py | 4 +- .../test_inversion_interferometer_util.py | 2 - .../inversion/test_inversion_util.py | 1 - 9 files changed, 192 insertions(+), 285 deletions(-) diff --git a/autoarray/dataset/interferometer/dataset.py b/autoarray/dataset/interferometer/dataset.py index 59a45b209..7a93e2cf3 100644 --- a/autoarray/dataset/interferometer/dataset.py +++ b/autoarray/dataset/interferometer/dataset.py @@ -91,7 +91,11 @@ def __init__( uv_wavelengths=uv_wavelengths, real_space_mask=real_space_mask ) - self.preprocessing_directory = Path(preprocessing_directory) if preprocessing_directory is not None else None + self.preprocessing_directory = ( + Path(preprocessing_directory) + if preprocessing_directory is not None + else None + ) @cached_property def grids(self): @@ -140,15 +144,11 @@ def from_fits( ) def w_tilde_preprocessing(self): - if self.preprocessing_directory.is_dir(): - filename = "{}/curvature_preload.fits".format(self.preprocessing_directory) if not self.preprocessing_directory.isfile(filename): - print( - "The file {} does not exist".format(filename) - ) + print("The file {} does not exist".format(filename)) logger.info("INTERFEROMETER - Computing W-Tilde... May take a moment.") curvature_preload = inversion_interferometer_util.w_tilde_curvature_preload_interferometer_from( diff --git a/autoarray/inversion/inversion/interferometer/inversion_interferometer_util.py b/autoarray/inversion/inversion/interferometer/inversion_interferometer_util.py index ef9848bb2..29580c06d 100644 --- a/autoarray/inversion/inversion/interferometer/inversion_interferometer_util.py +++ b/autoarray/inversion/inversion/interferometer/inversion_interferometer_util.py @@ -56,17 +56,15 @@ def w_tilde_data_interferometer_from( w_tilde_data = np.zeros(image_pixels) - weight_map_real = visibilities_real / noise_map_real ** 2.0 + weight_map_real = visibilities_real / noise_map_real**2.0 for ip0 in range(image_pixels): - value = 0.0 y = grid_radians_slim[ip0, 1] x = grid_radians_slim[ip0, 0] for vis_1d_index in range(uv_wavelengths.shape[0]): - value += weight_map_real[vis_1d_index] ** -2.0 * np.cos( 2.0 * np.pi @@ -81,7 +79,6 @@ def w_tilde_data_interferometer_from( return w_tilde_data - @numba_util.jit() def w_tilde_curvature_interferometer_from( noise_map_real: np.ndarray, @@ -121,12 +118,10 @@ def w_tilde_curvature_interferometer_from( for i in range(w_tilde.shape[0]): for j in range(i, w_tilde.shape[1]): - y_offset = grid_radians_slim[i, 1] - grid_radians_slim[j, 1] x_offset = grid_radians_slim[i, 0] - grid_radians_slim[j, 0] for vis_1d_index in range(uv_wavelengths.shape[0]): - w_tilde[i, j] += noise_map_real[vis_1d_index] ** -2.0 * np.cos( 2.0 * np.pi @@ -259,7 +254,6 @@ def w_tilde_curvature_preload_interferometer_from( for i in range(y_shape): for j in range(x_shape): - y_offset = grid_radians_2d[0, 0, 0] - grid_radians_2d[i, j, 0] x_offset = grid_radians_2d[0, 0, 1] - grid_radians_2d[i, j, 1] @@ -277,9 +271,7 @@ def w_tilde_curvature_preload_interferometer_from( for i in range(y_shape): for j in range(x_shape): - if j > 0: - y_offset = ( grid_radians_2d[0, -1, 0] - grid_radians_2d[i, grid_x_shape - j - 1, 0] @@ -303,9 +295,7 @@ def w_tilde_curvature_preload_interferometer_from( for i in range(y_shape): for j in range(x_shape): - if i > 0: - y_offset = ( grid_radians_2d[-1, 0, 0] - grid_radians_2d[grid_y_shape - i - 1, j, 0] @@ -329,9 +319,7 @@ def w_tilde_curvature_preload_interferometer_from( for i in range(y_shape): for j in range(x_shape): - if i > 0 and j > 0: - y_offset = ( grid_radians_2d[-1, -1, 0] - grid_radians_2d[grid_y_shape - i - 1, grid_x_shape - j - 1, 0] @@ -382,11 +370,9 @@ def w_tilde_via_preload_from(w_tilde_preload, native_index_for_slim_index): w_tilde_via_preload = np.zeros((slim_size, slim_size)) for i in range(slim_size): - i_y, i_x = native_index_for_slim_index[i] for j in range(i, slim_size): - j_y, j_x = native_index_for_slim_index[j] y_diff = j_y - i_y @@ -501,21 +487,17 @@ def curvature_matrix_via_w_tilde_curvature_preload_interferometer_from( curvature_matrix = np.zeros((pix_pixels, pix_pixels)) for ip0 in range(image_pixels): - ip0_y, ip0_x = native_index_for_slim_index[ip0] for ip0_pix in range(pix_size_for_sub_slim_index[ip0]): - ip0_weight = pix_weights_for_sub_slim_index[ip0, ip0_pix] sp0 = pix_indexes_for_sub_slim_index[ip0, ip0_pix] for ip1 in range(image_pixels): - ip1_y, ip1_x = native_index_for_slim_index[ip1] for ip1_pix in range(pix_size_for_sub_slim_index[ip1]): - ip1_weight = pix_weights_for_sub_slim_index[ip1, ip1_pix] sp1 = pix_indexes_for_sub_slim_index[ip1, ip1_pix] @@ -563,6 +545,7 @@ def mapped_reconstructed_visibilities_from( Welcome to the quagmire! """ + @numba_util.jit() def curvature_matrix_via_w_tilde_curvature_preload_interferometer_from_2( curvature_preload: np.ndarray, @@ -647,7 +630,6 @@ def curvature_matrix_via_w_tilde_curvature_preload_interferometer_from_2( return curvature_matrix - @numba_util.jit() def w_tilde_curvature_preload_interferometer_stage_1_from( noise_map_real: np.ndarray, @@ -655,7 +637,6 @@ def w_tilde_curvature_preload_interferometer_stage_1_from( shape_masked_pixels_2d: Tuple[int, int], grid_radians_2d: np.ndarray, ) -> np.ndarray: - y_shape = shape_masked_pixels_2d[0] x_shape = shape_masked_pixels_2d[1] @@ -669,7 +650,6 @@ def w_tilde_curvature_preload_interferometer_stage_1_from( for i in range(y_shape): for j in range(x_shape): - y_offset = grid_radians_2d[0, 0, 0] - grid_radians_2d[i, j, 0] x_offset = grid_radians_2d[0, 0, 1] - grid_radians_2d[i, j, 1] @@ -687,6 +667,7 @@ def w_tilde_curvature_preload_interferometer_stage_1_from( return curvature_preload_stage_1 + @numba_util.jit() def w_tilde_curvature_preload_interferometer_stage_2_from( noise_map_real: np.ndarray, @@ -694,7 +675,6 @@ def w_tilde_curvature_preload_interferometer_stage_2_from( shape_masked_pixels_2d: Tuple[int, int], grid_radians_2d: np.ndarray, ) -> np.ndarray: - y_shape = shape_masked_pixels_2d[0] x_shape = shape_masked_pixels_2d[1] @@ -708,9 +688,7 @@ def w_tilde_curvature_preload_interferometer_stage_2_from( for i in range(y_shape): for j in range(x_shape): - if j > 0: - y_offset = ( grid_radians_2d[0, -1, 0] - grid_radians_2d[i, grid_x_shape - j - 1, 0] @@ -742,7 +720,6 @@ def w_tilde_curvature_preload_interferometer_stage_3_from( shape_masked_pixels_2d: Tuple[int, int], grid_radians_2d: np.ndarray, ) -> np.ndarray: - y_shape = shape_masked_pixels_2d[0] x_shape = shape_masked_pixels_2d[1] @@ -756,9 +733,7 @@ def w_tilde_curvature_preload_interferometer_stage_3_from( for i in range(y_shape): for j in range(x_shape): - if i > 0: - y_offset = ( grid_radians_2d[-1, 0, 0] - grid_radians_2d[grid_y_shape - i - 1, j, 0] @@ -782,6 +757,7 @@ def w_tilde_curvature_preload_interferometer_stage_3_from( return curvature_preload_stage_3 + @numba_util.jit() def w_tilde_curvature_preload_interferometer_stage_4_from( noise_map_real: np.ndarray, @@ -789,7 +765,6 @@ def w_tilde_curvature_preload_interferometer_stage_4_from( shape_masked_pixels_2d: Tuple[int, int], grid_radians_2d: np.ndarray, ) -> np.ndarray: - y_shape = shape_masked_pixels_2d[0] x_shape = shape_masked_pixels_2d[1] @@ -803,9 +778,7 @@ def w_tilde_curvature_preload_interferometer_stage_4_from( for i in range(y_shape): for j in range(x_shape): - if i > 0 and j > 0: - y_offset = ( grid_radians_2d[-1, -1, 0] - grid_radians_2d[grid_y_shape - i - 1, grid_x_shape - j - 1, 0] @@ -830,19 +803,16 @@ def w_tilde_curvature_preload_interferometer_stage_4_from( return curvature_preload_stage_4 - - def w_tilde_curvature_preload_interferometer_in_stages_with_chunks_from( noise_map_real: np.ndarray, uv_wavelengths: np.ndarray, shape_masked_pixels_2d: Tuple[int, int], grid_radians_2d: np.ndarray, - stage = "1", + stage="1", chunk: int = 100, check=True, directory=None, ) -> np.ndarray: - if directory is None: raise NotImplementedError() @@ -920,15 +890,15 @@ def w_tilde_curvature_preload_interferometer_in_stages_with_chunks_from( size = size + chunk + @numba_util.jit() def w_tilde_curvature_preload_interferometer_stage_1_with_limits_placeholder_from( noise_map_real: np.ndarray, uv_wavelengths: np.ndarray, shape_masked_pixels_2d: Tuple[int, int], grid_radians_2d: np.ndarray, - limits: list = [] + limits: list = [], ) -> np.ndarray: - y_shape = shape_masked_pixels_2d[0] x_shape = shape_masked_pixels_2d[1] @@ -943,7 +913,6 @@ def w_tilde_curvature_preload_interferometer_stage_1_with_limits_placeholder_fro i_lower, i_upper = limits for i in range(i_lower, i_upper): for j in range(x_shape): - y_offset = grid_radians_2d[0, 0, 0] - grid_radians_2d[i, j, 0] x_offset = grid_radians_2d[0, 0, 1] - grid_radians_2d[i, j, 1] @@ -961,15 +930,15 @@ def w_tilde_curvature_preload_interferometer_stage_1_with_limits_placeholder_fro return curvature_preload_stage_1 + @numba_util.jit() def w_tilde_curvature_preload_interferometer_stage_2_with_limits_placeholder_from( noise_map_real: np.ndarray, uv_wavelengths: np.ndarray, shape_masked_pixels_2d: Tuple[int, int], grid_radians_2d: np.ndarray, - limits: list = [] + limits: list = [], ) -> np.ndarray: - y_shape = shape_masked_pixels_2d[0] x_shape = shape_masked_pixels_2d[1] @@ -984,9 +953,7 @@ def w_tilde_curvature_preload_interferometer_stage_2_with_limits_placeholder_fro i_lower, i_upper = limits for i in range(i_lower, i_upper): for j in range(x_shape): - if j > 0: - y_offset = ( grid_radians_2d[0, -1, 0] - grid_radians_2d[i, grid_x_shape - j - 1, 0] @@ -1010,15 +977,15 @@ def w_tilde_curvature_preload_interferometer_stage_2_with_limits_placeholder_fro return curvature_preload_stage_2 + @numba_util.jit() def w_tilde_curvature_preload_interferometer_stage_3_with_limits_placeholder_from( noise_map_real: np.ndarray, uv_wavelengths: np.ndarray, shape_masked_pixels_2d: Tuple[int, int], grid_radians_2d: np.ndarray, - limits: list = [] + limits: list = [], ) -> np.ndarray: - y_shape = shape_masked_pixels_2d[0] x_shape = shape_masked_pixels_2d[1] @@ -1033,9 +1000,7 @@ def w_tilde_curvature_preload_interferometer_stage_3_with_limits_placeholder_fro i_lower, i_upper = limits for i in range(i_lower, i_upper): for j in range(x_shape): - if i > 0: - y_offset = ( grid_radians_2d[-1, 0, 0] - grid_radians_2d[grid_y_shape - i - 1, j, 0] @@ -1059,15 +1024,15 @@ def w_tilde_curvature_preload_interferometer_stage_3_with_limits_placeholder_fro return curvature_preload_stage_3 + @numba_util.jit() def w_tilde_curvature_preload_interferometer_stage_4_with_limits_placeholder_from( noise_map_real: np.ndarray, uv_wavelengths: np.ndarray, shape_masked_pixels_2d: Tuple[int, int], grid_radians_2d: np.ndarray, - limits: list = [] + limits: list = [], ) -> np.ndarray: - y_shape = shape_masked_pixels_2d[0] x_shape = shape_masked_pixels_2d[1] @@ -1082,9 +1047,7 @@ def w_tilde_curvature_preload_interferometer_stage_4_with_limits_placeholder_fro i_lower, i_upper = limits for i in range(i_lower, i_upper): for j in range(x_shape): - if i > 0 and j > 0: - y_offset = ( grid_radians_2d[-1, -1, 0] - grid_radians_2d[grid_y_shape - i - 1, grid_x_shape - j - 1, 0] @@ -1109,13 +1072,8 @@ def w_tilde_curvature_preload_interferometer_stage_4_with_limits_placeholder_fro return curvature_preload_stage_4 - -def make_2d( - arr: mp.Array, - y_shape: int, - x_shape: int -) -> np.ndarray: - ''' +def make_2d(arr: mp.Array, y_shape: int, x_shape: int) -> np.ndarray: + """ Converts shared multiprocessing array into a non-square Numpy array of a given shape. Multiprocessing arrays must have only a single dimension. Parameters @@ -1131,8 +1089,8 @@ def make_2d( ------- para_result Reshaped array in Numpy array format. - ''' - para_result_np = np.frombuffer(arr.get_obj(), dtype='float64') + """ + para_result_np = np.frombuffer(arr.get_obj(), dtype="float64") para_result = para_result_np.reshape((y_shape, x_shape)) return para_result @@ -1145,8 +1103,9 @@ def parallel_preload( x_shape: int, i0: int, i1: int, - loop_number: int): - ''' + loop_number: int, +): + """ Runs the each loop in the curvature preload calculation by calling the associated JIT accelerated function. Parameters @@ -1175,34 +1134,59 @@ def parallel_preload( ------- none Updates shared object - ''' + """ if loop_number == 1: for i in range(i0, i1): - jit_loop_preload_1(noise_map_real, uv_wavelengths, grid_radians_2d, - curvature_preload, x_shape, i) + jit_loop_preload_1( + noise_map_real, + uv_wavelengths, + grid_radians_2d, + curvature_preload, + x_shape, + i, + ) elif loop_number == 2: for i in range(i0, i1): - jit_loop_preload_2(noise_map_real, uv_wavelengths, grid_radians_2d, - curvature_preload, x_shape, i) + jit_loop_preload_2( + noise_map_real, + uv_wavelengths, + grid_radians_2d, + curvature_preload, + x_shape, + i, + ) elif loop_number == 3: for i in range(i0, i1): - jit_loop_preload_3(noise_map_real, uv_wavelengths, grid_radians_2d, - curvature_preload, x_shape, i) + jit_loop_preload_3( + noise_map_real, + uv_wavelengths, + grid_radians_2d, + curvature_preload, + x_shape, + i, + ) elif loop_number == 4: for i in range(i0, i1): - jit_loop_preload_4(noise_map_real, uv_wavelengths, grid_radians_2d, - curvature_preload, x_shape, i) + jit_loop_preload_4( + noise_map_real, + uv_wavelengths, + grid_radians_2d, + curvature_preload, + x_shape, + i, + ) -@numba_util.jit(cache = True) +@numba_util.jit(cache=True) def jit_loop_preload_1( noise_map_real: np.ndarray, uv_wavelengths: np.ndarray, grid_radians_2d: np.ndarray, curvature_preload: np.ndarray, x_shape: int, - i: int): - ''' + i: int, +): + """ JIT-accelerated function for the first loop of the curvature preload calculation. Parameters @@ -1227,33 +1211,34 @@ def jit_loop_preload_1( ------- none Updates shared object - ''' + """ grid_y_shape = grid_radians_2d.shape[0] grid_x_shape = grid_radians_2d.shape[1] for j in range(x_shape): - y_offset = grid_radians_2d[0, 0, 0] - grid_radians_2d[i, j, 0] x_offset = grid_radians_2d[0, 0, 1] - grid_radians_2d[i, j, 1] for vis_1d_index in range(uv_wavelengths.shape[0]): - - curvature_preload[i, j] += noise_map_real[vis_1d_index - ] ** -2.0 * np.cos(2.0 * np.pi * ( - x_offset * uv_wavelengths[vis_1d_index, 0] - + y_offset * uv_wavelengths[vis_1d_index, 1] - ) + curvature_preload[i, j] += noise_map_real[vis_1d_index] ** -2.0 * np.cos( + 2.0 + * np.pi + * ( + x_offset * uv_wavelengths[vis_1d_index, 0] + + y_offset * uv_wavelengths[vis_1d_index, 1] ) + ) -@numba_util.jit(cache = True) +@numba_util.jit(cache=True) def jit_loop_preload_2( noise_map_real: np.ndarray, uv_wavelengths: np.ndarray, grid_radians_2d: np.ndarray, curvature_preload: np.ndarray, x_shape: int, - i: int): - ''' + i: int, +): + """ JIT-accelerated function for the second loop of the curvature preload calculation. Parameters @@ -1278,39 +1263,41 @@ def jit_loop_preload_2( ------- none Updates shared object - ''' + """ grid_y_shape = grid_radians_2d.shape[0] grid_x_shape = grid_radians_2d.shape[1] for j in range(x_shape): if j > 0: - y_offset = ( - grid_radians_2d[0, -1, 0] - - grid_radians_2d[i, grid_x_shape - j - 1, 0] + grid_radians_2d[0, -1, 0] - grid_radians_2d[i, grid_x_shape - j - 1, 0] ) x_offset = ( - grid_radians_2d[0, -1, 1] - - grid_radians_2d[i, grid_x_shape - j - 1, 1] + grid_radians_2d[0, -1, 1] - grid_radians_2d[i, grid_x_shape - j - 1, 1] ) for vis_1d_index in range(uv_wavelengths.shape[0]): - curvature_preload[i, -j] += noise_map_real[vis_1d_index - ] ** -2.0 * np.cos(2.0 * np.pi * ( + curvature_preload[i, -j] += noise_map_real[ + vis_1d_index + ] ** -2.0 * np.cos( + 2.0 + * np.pi + * ( x_offset * uv_wavelengths[vis_1d_index, 0] + y_offset * uv_wavelengths[vis_1d_index, 1] ) ) -@numba_util.jit(cache = True) +@numba_util.jit(cache=True) def jit_loop_preload_3( noise_map_real: np.ndarray, uv_wavelengths: np.ndarray, grid_radians_2d: np.ndarray, curvature_preload: np.ndarray, x_shape: int, - i: int): - ''' + i: int, +): + """ JIT-accelerated function for the third loop of the curvature preload calculation. Parameters @@ -1335,19 +1322,16 @@ def jit_loop_preload_3( ------- none Updates shared object - ''' + """ grid_y_shape = grid_radians_2d.shape[0] grid_x_shape = grid_radians_2d.shape[1] for j in range(x_shape): if i > 0: - y_offset = ( - grid_radians_2d[-1, 0, 0] - - grid_radians_2d[grid_y_shape - i - 1, j, 0] + grid_radians_2d[-1, 0, 0] - grid_radians_2d[grid_y_shape - i - 1, j, 0] ) x_offset = ( - grid_radians_2d[-1, 0, 1] - - grid_radians_2d[grid_y_shape - i - 1, j, 1] + grid_radians_2d[-1, 0, 1] - grid_radians_2d[grid_y_shape - i - 1, j, 1] ) for vis_1d_index in range(uv_wavelengths.shape[0]): @@ -1363,15 +1347,16 @@ def jit_loop_preload_3( ) -@numba_util.jit(cache = True) +@numba_util.jit(cache=True) def jit_loop_preload_4( noise_map_real: np.ndarray, uv_wavelengths: np.ndarray, grid_radians_2d: np.ndarray, curvature_preload: np.ndarray, x_shape: int, - i: int): - ''' + i: int, +): + """ JIT-accelerated function for the forth loop of the curvature preload calculation. Parameters @@ -1396,12 +1381,11 @@ def jit_loop_preload_4( ------- none Updates shared object - ''' + """ grid_y_shape = grid_radians_2d.shape[0] grid_x_shape = grid_radians_2d.shape[1] for j in range(x_shape): if i > 0 and j > 0: - y_offset = ( grid_radians_2d[-1, -1, 0] - grid_radians_2d[grid_y_shape - i - 1, grid_x_shape - j - 1, 0] @@ -1425,16 +1409,12 @@ def jit_loop_preload_4( try: - import numba from numba import prange - - @numba.jit("void(f8[:,:], i8)", nopython=True, parallel=True, cache = True) - def jit_loop2( - curvature_matrix: np.ndarray, - pix_pixels: int): - ''' + @numba.jit("void(f8[:,:], i8)", nopython=True, parallel=True, cache=True) + def jit_loop2(curvature_matrix: np.ndarray, pix_pixels: int): + """ Performs second stage of curvature matrix calculation using Numba parallelisation and JIT. Parameters @@ -1448,28 +1428,29 @@ def jit_loop2( ------- none Updates shared object. - ''' + """ curvature_matrix_temp = curvature_matrix.copy() for i in prange(pix_pixels): for j in range(pix_pixels): - curvature_matrix[i, j] = curvature_matrix_temp[i, j] + curvature_matrix_temp[j, i] + curvature_matrix[i, j] = ( + curvature_matrix_temp[i, j] + curvature_matrix_temp[j, i] + ) except ModuleNotFoundError: - pass -@numba_util.jit(cache = True) +@numba_util.jit(cache=True) def jit_loop3( curvature_matrix: np.ndarray, pix_indexes_for_sub_slim_index: np.ndarray, pix_size_for_sub_slim_index: np.ndarray, pix_weights_for_sub_slim_index: np.ndarray, preload: np.float64, - image_pixels: int - ) -> np.ndarray: - ''' + image_pixels: int, +) -> np.ndarray: + """ Third stage of curvature matrix calculation. Parameters @@ -1491,11 +1472,10 @@ def jit_loop3( ------- ndarray Fully computed curvature preload matrix F. - ''' + """ for ip0 in range(image_pixels): for ip0_pix in range(pix_size_for_sub_slim_index[ip0]): for ip1_pix in range(pix_size_for_sub_slim_index[ip0]): - sp0 = pix_indexes_for_sub_slim_index[ip0, ip0_pix] sp1 = pix_indexes_for_sub_slim_index[ip0, ip1_pix] @@ -1519,8 +1499,9 @@ def parallel_loop1( curvature_matrix: np.ndarray, i0: int, i1: int, - lock: mp.Lock): - ''' + lock: mp.Lock, +): + """ This function prepares the first part of the curvature matrix calculation and is called by a multiprocessing process. Parameters @@ -1550,22 +1531,28 @@ def parallel_loop1( ------ none Updates shared object, doesn not return anything. - ''' + """ print(f"calling parallel_loop1 for process {mp.current_process().pid}.") image_pixels = len(native_index_for_slim_index) for ip0 in range(i0, i1): ip0_y, ip0_x = native_index_for_slim_index[ip0] - #print(f"Processing ip0={ip0}, ip0_y={ip0_y}, ip0_x={ip0_x}") + # print(f"Processing ip0={ip0}, ip0_y={ip0_y}, ip0_x={ip0_x}") for ip0_pix in range(pix_size_for_sub_slim_index[ip0]): sp0 = pix_indexes_for_sub_slim_index[ip0, ip0_pix] - result_vector = jit_calc_loop1(image_pixels, - native_index_for_slim_index, - pix_indexes_for_sub_slim_index, - pix_size_for_sub_slim_index, - pix_weights_for_sub_slim_index, - curvature_preload, - curvature_matrix[sp0, :].shape, - ip0, ip0_pix, i1, ip0_y, ip0_x) + result_vector = jit_calc_loop1( + image_pixels, + native_index_for_slim_index, + pix_indexes_for_sub_slim_index, + pix_size_for_sub_slim_index, + pix_weights_for_sub_slim_index, + curvature_preload, + curvature_matrix[sp0, :].shape, + ip0, + ip0_pix, + i1, + ip0_y, + ip0_x, + ) with lock: curvature_matrix[sp0, :] += result_vector print(f"finished parallel_loop1 for process {mp.current_process().pid}.") @@ -1606,6 +1593,7 @@ def parallel_loop1_ChatGPT( # NOTE: THIS DID NOT FIX THE ISSUE ON COSMA ... np.add.at(curvature_matrix, np.nonzero(local_results), local_results[np.nonzero(local_results)]) """ + def parallel_loop1_ChatGPT( curvature_preload: np.ndarray, pix_indexes_for_sub_slim_index: np.ndarray, @@ -1615,9 +1603,8 @@ def parallel_loop1_ChatGPT( curvature_matrix: np.ndarray, i0: int, i1: int, - lock: mp.Lock + lock: mp.Lock, ): - print(f"calling parallel_loop1 for process {mp.current_process().pid}.") image_pixels = len(native_index_for_slim_index) @@ -1629,14 +1616,20 @@ def parallel_loop1_ChatGPT( ip0_y, ip0_x = native_index_for_slim_index[ip0] for ip0_pix in range(pix_size_for_sub_slim_index[ip0]): sp0 = pix_indexes_for_sub_slim_index[ip0, ip0_pix] - result_vector = jit_calc_loop1(image_pixels, - native_index_for_slim_index, - pix_indexes_for_sub_slim_index, - pix_size_for_sub_slim_index, - pix_weights_for_sub_slim_index, - curvature_preload, - local_curvature_matrix[sp0, :].shape, - ip0, ip0_pix, i1, ip0_y, ip0_x) + result_vector = jit_calc_loop1( + image_pixels, + native_index_for_slim_index, + pix_indexes_for_sub_slim_index, + pix_size_for_sub_slim_index, + pix_weights_for_sub_slim_index, + curvature_preload, + local_curvature_matrix[sp0, :].shape, + ip0, + ip0_pix, + i1, + ip0_y, + ip0_x, + ) local_curvature_matrix[sp0, :] += result_vector # Write the local results to the shared memory with a single lock acquisition @@ -1645,10 +1638,12 @@ def parallel_loop1_ChatGPT( curvature_matrix += local_curvature_matrix print(f"finished parallel_loop1 for process {mp.current_process().pid}.") + + # ---------------------------------------------------------------------------- # -@numba_util.jit(cache = True) +@numba_util.jit(cache=True) def jit_calc_loop1( image_pixels: int, native_index_for_slim_index: np.ndarray, @@ -1661,8 +1656,9 @@ def jit_calc_loop1( ip0_pix: int, i1: int, ip0_y: int, - ip0_x: int) -> np.ndarray: - ''' + ip0_x: int, +) -> np.ndarray: + """ Performs first stage of curvature matrix calculation in parallel using JIT. Returns a single column of the curvature matrix per function call. Parameters @@ -1696,17 +1692,15 @@ def jit_calc_loop1( ------- result_vector The column of the curvature matrix calculated in this loop iteration for this subprocess. - ''' + """ result_vector = np.zeros(result_vector_shape) ip0_weight = pix_weights_for_sub_slim_index[ip0, ip0_pix] for ip1 in range(ip0 + 1, image_pixels): - ip1_y, ip1_x = native_index_for_slim_index[ip1] for ip1_pix in range(pix_size_for_sub_slim_index[ip1]): - sp1 = pix_indexes_for_sub_slim_index[ip1, ip1_pix] ip1_weight = pix_weights_for_sub_slim_index[ip1, ip1_pix] @@ -1718,7 +1712,6 @@ def jit_calc_loop1( return result_vector - def curvature_matrix_via_w_tilde_curvature_preload_interferometer_para_from( curvature_preload: np.ndarray, pix_indexes_for_sub_slim_index: np.ndarray, @@ -1726,7 +1719,8 @@ def curvature_matrix_via_w_tilde_curvature_preload_interferometer_para_from( pix_weights_for_sub_slim_index: np.ndarray, native_index_for_slim_index: np.ndarray, pix_pixels: int, - n_processes: int = mp.cpu_count()) -> np.ndarray: + n_processes: int = mp.cpu_count(), +) -> np.ndarray: """ Returns the curvature matrix `F` (see Warren & Dye 2003) by computing it using `w_tilde_preload` (see `w_tilde_preload_interferometer_from`) for an interferometer inversion. @@ -1772,43 +1766,52 @@ def curvature_matrix_via_w_tilde_curvature_preload_interferometer_para_from( The curvature matrix `F` (see Warren & Dye 2003). """ - print("calling \'curvature_matrix_via_w_tilde_curvature_preload_interferometer_para_from\'.") + print( + "calling 'curvature_matrix_via_w_tilde_curvature_preload_interferometer_para_from'." + ) preload = curvature_preload[0, 0] image_pixels = len(native_index_for_slim_index) # Make sure there isn't more cores assigned than there is indices to loop over - if n_processes > image_pixels: + if n_processes > image_pixels: n_processes = image_pixels # Set up parallel code - idx_diff = int(image_pixels/n_processes) + idx_diff = int(image_pixels / n_processes) idxs = [] for n in range(n_processes): - idxs.append(idx_diff*n) + idxs.append(idx_diff * n) idxs.append(len(native_index_for_slim_index)) idx_access_list = [] - for i in range(len(idxs)-1): + for i in range(len(idxs) - 1): id0 = idxs[i] - id1 = idxs[i+1] + id1 = idxs[i + 1] idx_access_list.append([id0, id1]) lock = mp.Lock() - para_result_jit_arr = mp.Array('d', pix_pixels*pix_pixels) + para_result_jit_arr = mp.Array("d", pix_pixels * pix_pixels) # Run first loop in parallel print("starting 1st loop.") processes = [ - mp.Process(target = parallel_loop1, - args = (curvature_preload, - pix_indexes_for_sub_slim_index, - pix_size_for_sub_slim_index, - pix_weights_for_sub_slim_index, - native_index_for_slim_index, - make_2d(para_result_jit_arr, pix_pixels, pix_pixels), - i0, i1, - lock)) for i0, i1 in idx_access_list] + mp.Process( + target=parallel_loop1, + args=( + curvature_preload, + pix_indexes_for_sub_slim_index, + pix_size_for_sub_slim_index, + pix_weights_for_sub_slim_index, + native_index_for_slim_index, + make_2d(para_result_jit_arr, pix_pixels, pix_pixels), + i0, + i1, + lock, + ), + ) + for i0, i1 in idx_access_list + ] """ processes = [ @@ -1824,16 +1827,12 @@ def curvature_matrix_via_w_tilde_curvature_preload_interferometer_para_from( for i, p in enumerate(processes): p.start() time.sleep(0.01) - #logging.info(f"Started process {p.pid}.") - print( - "process {} started (id = {}).".format(i, p.pid) - ) + # logging.info(f"Started process {p.pid}.") + print("process {} started (id = {}).".format(i, p.pid)) for j, p in enumerate(processes): p.join() - #logging.info(f"Process {p.pid} finished.") - print( - "process {} finished (id = {}).".format(j, p.pid) - ) + # logging.info(f"Process {p.pid} finished.") + print("process {} finished (id = {}).".format(j, p.pid)) print("finished 1st loop.") # Run second loop @@ -1844,11 +1843,13 @@ def curvature_matrix_via_w_tilde_curvature_preload_interferometer_para_from( # Run final loop print("starting 3rd loop.") - curvature_matrix = jit_loop3(curvature_matrix, - pix_indexes_for_sub_slim_index, - pix_size_for_sub_slim_index, - pix_weights_for_sub_slim_index, - preload, - image_pixels) + curvature_matrix = jit_loop3( + curvature_matrix, + pix_indexes_for_sub_slim_index, + pix_size_for_sub_slim_index, + pix_weights_for_sub_slim_index, + preload, + image_pixels, + ) print("finished 3rd loop.") return curvature_matrix diff --git a/autoarray/inversion/inversion/inversion_util.py b/autoarray/inversion/inversion/inversion_util.py index d25f2ec68..178ce08ac 100644 --- a/autoarray/inversion/inversion/inversion_util.py +++ b/autoarray/inversion/inversion/inversion_util.py @@ -328,6 +328,3 @@ def preconditioner_matrix_via_mapping_matrix_from( return ( preconditioner_noise_normalization * curvature_matrix ) + regularization_matrix - - - diff --git a/autoarray/structures/abstract_structure.py b/autoarray/structures/abstract_structure.py index d6d915dff..193680980 100644 --- a/autoarray/structures/abstract_structure.py +++ b/autoarray/structures/abstract_structure.py @@ -88,7 +88,3 @@ def total_pixels(self) -> int: def trimmed_after_convolution_from(self, kernel_shape) -> "Structure": raise NotImplementedError - - @property - def hdu_for_output(self): - raise NotImplementedError diff --git a/autoarray/structures/arrays/array_1d_util.py b/autoarray/structures/arrays/array_1d_util.py index 67a79dbcc..175dddd8e 100644 --- a/autoarray/structures/arrays/array_1d_util.py +++ b/autoarray/structures/arrays/array_1d_util.py @@ -190,40 +190,6 @@ def array_1d_via_indexes_1d_from( return array_1d_native -def hdu_for_output_from( - array_1d: np.ndarray, - header_dict: Optional[dict] = None, -): - """ - Returns the HDU which can be used to output an array to a .fits file. - - Parameters - ---------- - array_1d - The 1D array that is written to fits. - header_dict - A dictionary of values that are written to the header of the .fits file. - - Returns - ------- - hdu - The HDU containing the data and its header which can then be written to .fits. - - Examples - -------- - array_1d = np.ones((5,5)) - hdu_for_output_from(array_1d=array_1d, file_path='/path/to/file/filename.fits', overwrite=True) - """ - - header = fits.Header() - - if header_dict is not None: - for key, value in header_dict.items(): - header.append((key, value, [""])) - - return fits.PrimaryHDU(array_1d, header) - - def numpy_array_1d_to_fits( array_1d: np.ndarray, file_path: Union[Path, str], diff --git a/autoarray/structures/arrays/array_2d_util.py b/autoarray/structures/arrays/array_2d_util.py index 230f366e9..cf618a944 100644 --- a/autoarray/structures/arrays/array_2d_util.py +++ b/autoarray/structures/arrays/array_2d_util.py @@ -710,58 +710,6 @@ def array_2d_native_complex_via_indexes_from( return array_2d -def hdu_for_output_from( - array_2d: np.ndarray, header_dict: Optional[dict] = None, ext_name : Optional[str] = None, return_as_primary : bool = False -) -> Union[fits.PrimaryHDU, fits.ImageHDU]: - """ - Returns the HDU which can be used to output an array to a .fits file. - - Before outputting a NumPy array, the array may be flipped upside-down using np.flipud depending on the project - config files. This is for Astronomy projects so that structures appear the same orientation as ``.fits`` files - loaded in DS9. - - The output .fits files may contain multiple HDUs comprising different images. Conventionally, the first array, - the `PrimaryHDU`, contains the 2D mask applied to the data and the remaining HDUs contain the data itself. - - Parameters - ---------- - array_2d - The 2D array that is written to fits. - header_dict - A dictionary of values that are written to the header of the .fits file. - ext_name - The name of the extension in the fits file, which displays in the header of the fits file and is visible - for example when the .fits is loaded in DS9. - return_as_primary - Whether the HDU is returned as a PrimaryHDU or ImageHDU. - - Returns - ------- - hdu - The HDU containing the data and its header which can then be written to .fits. - - Examples - -------- - array_2d = np.ones((5,5)) - hdu_for_output_from(array_2d=array_2d, header_dict={"Example": 0.5}, ext_name="data", return_as_primary=True) - """ - header = fits.Header() - - header["EXTNAME"] = ext_name.upper() - - if header_dict is not None: - for key, value in header_dict.items(): - header.append((key, value, [""])) - - flip_for_ds9 = conf.instance["general"]["fits"]["flip_for_ds9"] - - if flip_for_ds9: - array_2d = np.flipud(array_2d) - - if return_as_primary: - return fits.PrimaryHDU(array_2d, header=header) - return fits.ImageHDU(array_2d, header=header) - def numpy_array_2d_to_fits( array_2d: np.ndarray, diff --git a/autoarray/structures/arrays/uniform_2d.py b/autoarray/structures/arrays/uniform_2d.py index cbc37a512..d29c43f81 100644 --- a/autoarray/structures/arrays/uniform_2d.py +++ b/autoarray/structures/arrays/uniform_2d.py @@ -613,7 +613,9 @@ def trimmed_after_convolution_from( store_native=self.store_native, ) - def hdu_for_output_from(self, ext_name : Optional[str] = None, return_as_primary : bool = False) -> Union[fits.PrimaryHDU, fits.ImageHDU]: + def hdu_for_output_from( + self, ext_name: Optional[str] = None, return_as_primary: bool = False + ) -> Union[fits.PrimaryHDU, fits.ImageHDU]: """ The array as an HDU object, which can be output to a .fits file. diff --git a/test_autoarray/inversion/inversion/interferometer/test_inversion_interferometer_util.py b/test_autoarray/inversion/inversion/interferometer/test_inversion_interferometer_util.py index b1912d81b..8875ea378 100644 --- a/test_autoarray/inversion/inversion/interferometer/test_inversion_interferometer_util.py +++ b/test_autoarray/inversion/inversion/interferometer/test_inversion_interferometer_util.py @@ -68,7 +68,6 @@ def test__data_vector_via_transformed_mapping_matrix_from(): assert (data_vector_complex_via_blurred == data_vector_via_transformed).all() - def test__inversion_interferometer__via_mapper( interferometer_7_no_fft, rectangular_mapper_7x7_3x3, @@ -400,4 +399,3 @@ def test__identical_inversion_source_and_image_loops(): assert inversion_image_loop.mapped_reconstructed_data == pytest.approx( inversion_source_loop.mapped_reconstructed_data, 1.0e-2 ) - diff --git a/test_autoarray/inversion/inversion/test_inversion_util.py b/test_autoarray/inversion/inversion/test_inversion_util.py index 5bc7d29e5..86b722812 100644 --- a/test_autoarray/inversion/inversion/test_inversion_util.py +++ b/test_autoarray/inversion/inversion/test_inversion_util.py @@ -247,4 +247,3 @@ def test__preconditioner_matrix_via_mapping_matrix_from(): preconditioner_matrix == np.array([[5.0, 2.0, 3.0], [4.0, 9.0, 6.0], [7.0, 8.0, 13.0]]) ).all() - From a0144abd876657d65c678deb2b65e83c9ab7b099 Mon Sep 17 00:00:00 2001 From: James Nightingale Date: Fri, 21 Mar 2025 11:42:10 +0000 Subject: [PATCH 03/13] remove all fits utils --- autoarray/dataset/imaging/dataset.py | 2 +- autoarray/mask/abstract_mask.py | 23 +-- autoarray/structures/arrays/array_1d_util.py | 73 +------- autoarray/structures/arrays/array_2d_util.py | 176 +------------------ 4 files changed, 7 insertions(+), 267 deletions(-) diff --git a/autoarray/dataset/imaging/dataset.py b/autoarray/dataset/imaging/dataset.py index 78449ee47..bc529ef96 100644 --- a/autoarray/dataset/imaging/dataset.py +++ b/autoarray/dataset/imaging/dataset.py @@ -108,7 +108,7 @@ def __init__( kernel_shape=psf.shape_native, mask_pad_value=1 ) ) - print(over_sample_size_lp.shape_native) + over_sample_size_pixelization = ( over_sample_util.over_sample_size_convert_to_array_2d_from( over_sample_size=over_sample_size_pixelization, mask=data.mask diff --git a/autoarray/mask/abstract_mask.py b/autoarray/mask/abstract_mask.py index 4c8aa0a34..ab33a7e11 100644 --- a/autoarray/mask/abstract_mask.py +++ b/autoarray/mask/abstract_mask.py @@ -73,18 +73,6 @@ def pixel_scale(self) -> float: For a mask with dimensions two or above check that are pixel scales are the same, and if so return this single value as a float. """ - # for pixel_scale in self.pixel_scales: - # if abs(pixel_scale - self.pixel_scales[0]) > 1.0e-8: - # logger.warning( - # f""" - # The Mask has different pixel scales in each dimensions, which are {self.pixel_scales}. - # - # This is not expected, and will lead to unexpected behaviour in the grid and mask classes. - # The code will continue to run, but you should check that the pixel scales are as you expect and - # that associated data structures (e.g. grids) are behaving as you expect. - # """ - # ) - return self.pixel_scales[0] @property @@ -99,13 +87,10 @@ def pixel_scale_header(self) -> Dict: ------- A dictionary containing the pixel scale of the mask, which can be output to a .fits file. """ - try: - return {"PIXSCALE": self.pixel_scale} - except exc.MaskException: - return { - "PIXSCALEY": self.pixel_scales[0], - "PIXSCALEX": self.pixel_scales[1], - } + return { + "PIXSCAY": self.pixel_scales[0], + "PIXSCAX": self.pixel_scales[1], + } @property def dimensions(self) -> int: diff --git a/autoarray/structures/arrays/array_1d_util.py b/autoarray/structures/arrays/array_1d_util.py index 175dddd8e..7efe7d8b1 100644 --- a/autoarray/structures/arrays/array_1d_util.py +++ b/autoarray/structures/arrays/array_1d_util.py @@ -1,9 +1,6 @@ from __future__ import annotations -import os import numpy as np -from astropy.io import fits -from pathlib import Path -from typing import TYPE_CHECKING, List, Optional, Union +from typing import TYPE_CHECKING, List, Union if TYPE_CHECKING: from autoarray.mask.mask_1d import Mask1D @@ -190,71 +187,3 @@ def array_1d_via_indexes_1d_from( return array_1d_native -def numpy_array_1d_to_fits( - array_1d: np.ndarray, - file_path: Union[Path, str], - overwrite: bool = False, - header_dict: Optional[dict] = None, -): - """ - Write a 1D NumPy array to a .fits file. - - Parameters - ---------- - array_1d - The 1D array that is written to fits. - file_path - The full path of the file that is output, including the file name and ``.fits`` extension. - overwrite - If `True` and a file already exists with the input file_path the .fits file is overwritten. If False, an error - will be raised. - header_dict - A dictionary of values that are written to the header of the .fits file. - - Returns - ------- - None - - Examples - -------- - array_1d = np.ones((5,)) - numpy_array_to_fits(array_1d=array_1d, file_path='/path/to/file/filename.fits', overwrite=True) - """ - - file_dir = os.path.split(file_path)[0] - - if not os.path.exists(file_dir): - os.makedirs(file_dir) - - if overwrite and os.path.exists(file_path): - os.remove(file_path) - - hdu = hdu_for_output_from(array_1d=array_1d, header_dict=header_dict) - hdu.writeto(file_path) - - -def numpy_array_1d_via_fits_from(file_path: Union[Path, str], hdu: int): - """ - Read a 1D NumPy array from a .fits file. - - After loading the NumPy array, the array is flipped upside-down using np.flipud. This is so that the structures - appear the same orientation as .fits files loaded in DS9. - - Parameters - ---------- - file_path - The full path of the file that is loaded, including the file name and ``.fits`` extension. - hdu - The HDU extension of the array that is loaded from the .fits file. - - Returns - ------- - ndarray - The NumPy array that is loaded from the .fits file. - - Examples - -------- - array_2d = numpy_array_via_fits(file_path='/path/to/file/filename.fits', hdu=0) - """ - hdu_list = fits.open(file_path) - return np.array(hdu_list[hdu].data) diff --git a/autoarray/structures/arrays/array_2d_util.py b/autoarray/structures/arrays/array_2d_util.py index cf618a944..4eb196bf8 100644 --- a/autoarray/structures/arrays/array_2d_util.py +++ b/autoarray/structures/arrays/array_2d_util.py @@ -1,14 +1,10 @@ from __future__ import annotations -from astropy.io import fits import numpy as np -import os -from pathlib import Path -from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union +from typing import TYPE_CHECKING, List, Tuple, Union if TYPE_CHECKING: from autoarray.mask.mask_2d import Mask2D -from autoconf import conf from autoarray import numba_util from autoarray.mask import mask_2d_util @@ -711,173 +707,3 @@ def array_2d_native_complex_via_indexes_from( -def numpy_array_2d_to_fits( - array_2d: np.ndarray, - file_path: Union[Path, str], - overwrite: bool = False, - header_dict: Optional[dict] = None, -): - """ - Write a 2D NumPy array to a .fits file. - - Before outputting a NumPy array, the array may be flipped upside-down using np.flipud depending on the project - config files. This is for Astronomy projects so that structures appear the same orientation as ``.fits`` files - loaded in DS9. - - Parameters - ---------- - array_2d - The 2D array that is written to fits. - file_path - The full path of the file that is output, including the file name and ``.fits`` extension. - overwrite - If `True` and a file already exists with the input file_path the .fits file is overwritten. If `False`, an - error is raised. - header_dict - A dictionary of values that are written to the header of the .fits file. - - Returns - ------- - None - - Examples - -------- - array_2d = np.ones((5,5)) - numpy_array_to_fits(array_2d=array_2d, file_path='/path/to/file/filename.fits', overwrite=True) - """ - - file_dir = os.path.split(file_path)[0] - - if not os.path.exists(file_dir): - os.makedirs(file_dir) - - if overwrite and os.path.exists(file_path): - os.remove(file_path) - - hdu = hdu_for_output_from(array_2d=array_2d, header_dict=header_dict) - - hdu.writeto(file_path) - - -def numpy_array_2d_via_fits_from( - file_path: Union[Path, str], hdu: int, do_not_scale_image_data: bool = False -): - """ - Read a 2D NumPy array from a .fits file. - - After loading the NumPy array, the array is flipped upside-down using np.flipud. This is so that the structures - appear the same orientation as .fits files loaded in DS9. - - Parameters - ---------- - file_path - The full path of the file that is loaded, including the file name and ``.fits`` extension. - hdu - The HDU extension of the array that is loaded from the .fits file. - do_not_scale_image_data - If True, the .fits file is not rescaled automatically based on the .fits header info. - - Returns - ------- - ndarray - The NumPy array that is loaded from the .fits file. - - Examples - -------- - array_2d = numpy_array_2d_via_fits_from(file_path='/path/to/file/filename.fits', hdu=0) - """ - hdu_list = fits.open(file_path, do_not_scale_image_data=do_not_scale_image_data) - - flip_for_ds9 = conf.instance["general"]["fits"]["flip_for_ds9"] - - if flip_for_ds9: - return np.flipud(np.array(hdu_list[hdu].data)).astype("float64") - return np.array(hdu_list[hdu].data).astype("float64") - - -def header_obj_from(file_path: Union[Path, str], hdu: int) -> Dict: - """ - Read a 2D NumPy array from a .fits file. - - After loading the NumPy array, the array is flipped upside-down using np.flipud. This is so that the structures - appear the same orientation as .fits files loaded in DS9. - - Parameters - ---------- - file_path - The full path of the file that is loaded, including the file name and ``.fits`` extension. - hdu - The HDU extension of the array that is loaded from the .fits file. - do_not_scale_image_data - If True, the .fits file is not rescaled automatically based on the .fits header info. - - Returns - ------- - dict - The header dictionary. - - Examples - -------- - array_2d = numpy_array_2d_via_fits_from(file_path='/path/to/file/filename.fits', hdu=0) - """ - 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) From 99e31f21907bb28f8fd79221426ff68d2b7ac25c Mon Sep 17 00:00:00 2001 From: James Nightingale Date: Fri, 21 Mar 2025 11:44:40 +0000 Subject: [PATCH 04/13] delete all from_primary_hdu --- autoarray/mask/mask_1d.py | 44 ---------------- autoarray/mask/mask_2d.py | 44 ---------------- autoarray/structures/arrays/kernel_2d.py | 44 ---------------- autoarray/structures/arrays/uniform_1d.py | 45 ----------------- autoarray/structures/arrays/uniform_2d.py | 50 +------------------ test_autoarray/mask/test_mask_1d.py | 23 --------- test_autoarray/mask/test_mask_2d.py | 23 --------- .../structures/arrays/test_kernel_2d.py | 23 --------- .../structures/arrays/test_uniform_1d.py | 23 --------- .../structures/arrays/test_uniform_2d.py | 24 --------- 10 files changed, 2 insertions(+), 341 deletions(-) diff --git a/autoarray/mask/mask_1d.py b/autoarray/mask/mask_1d.py index 0f2388aac..56bbb196a 100644 --- a/autoarray/mask/mask_1d.py +++ b/autoarray/mask/mask_1d.py @@ -154,50 +154,6 @@ def from_fits( origin=origin, ) - @classmethod - def from_primary_hdu( - cls, - primary_hdu: fits.PrimaryHDU, - origin: Tuple[float, float] = (0.0, 0.0), - ) -> "Mask1D": - """ - Returns an ``Mask1D`` by from a `PrimaryHDU` object which has been loaded via `astropy.fits` - - This assumes that the `header` of the `PrimaryHDU` contains an entry named `PIXSCALE` which gives the - pixel-scale of the array. - - For a full description of ``Mask1D`` objects, including a description of the ``slim`` and ``native`` attribute - used by the API, see - the :meth:`Mask1D class API documentation `. - - Parameters - ---------- - primary_hdu - The `PrimaryHDU` object which has already been loaded from a .fits file via `astropy.fits` and contains - the array data and the pixel-scale in the header with an entry named `PIXSCALE`. - origin - The (y,x) scaled units origin of the coordinate system. - - Examples - -------- - - .. code-block:: python - - from astropy.io import fits - import autoarray as aa - - primary_hdu = fits.open("path/to/file.fits") - - array_1d = aa.Mask1D.from_primary_hdu( - primary_hdu=primary_hdu, - ) - """ - return cls( - mask=primary_hdu.data.astype("bool"), - pixel_scales=primary_hdu.header["PIXSCALE"], - origin=origin, - ) - @property def shape_native(self) -> Tuple[int]: return self.shape diff --git a/autoarray/mask/mask_2d.py b/autoarray/mask/mask_2d.py index 18a4c8fea..af5b18818 100644 --- a/autoarray/mask/mask_2d.py +++ b/autoarray/mask/mask_2d.py @@ -656,50 +656,6 @@ def from_fits( return mask - @classmethod - def from_primary_hdu( - cls, - primary_hdu: fits.PrimaryHDU, - origin: Tuple[float, float] = (0.0, 0.0), - ) -> "Mask2D": - """ - Returns an ``Mask2D`` by from a `PrimaryHDU` object which has been loaded via `astropy.fits` - - This assumes that the `header` of the `PrimaryHDU` contains an entry named `PIXSCALE` which gives the - pixel-scale of the array. - - For a full description of ``Mask2D`` objects, including a description of the ``slim`` and ``native`` attribute - used by the API, see - the :meth:`Mask2D class API documentation `. - - Parameters - ---------- - primary_hdu - The `PrimaryHDU` object which has already been loaded from a .fits file via `astropy.fits` and contains - the array data and the pixel-scale in the header with an entry named `PIXSCALE`. - origin - The (y,x) scaled units origin of the coordinate system. - - Examples - -------- - - .. code-block:: python - - from astropy.io import fits - import autoarray as aa - - primary_hdu = fits.open("path/to/file.fits") - - array_2d = aa.Mask2D.from_primary_hdu( - primary_hdu=primary_hdu, - ) - """ - return cls( - mask=cls.flip_hdu_for_ds9(primary_hdu.data.astype("float")), - pixel_scales=primary_hdu.header["PIXSCALE"], - origin=origin, - ) - @property def shape_native(self) -> Tuple[int, ...]: return self.shape diff --git a/autoarray/structures/arrays/kernel_2d.py b/autoarray/structures/arrays/kernel_2d.py index 0bef160dc..435011fd9 100644 --- a/autoarray/structures/arrays/kernel_2d.py +++ b/autoarray/structures/arrays/kernel_2d.py @@ -355,50 +355,6 @@ def from_fits( header=Header(header_sci_obj=header_sci_obj, header_hdu_obj=header_hdu_obj), ) - @classmethod - def from_primary_hdu( - cls, - primary_hdu: fits.PrimaryHDU, - origin: Tuple[float, float] = (0.0, 0.0), - ) -> "Kernel2D": - """ - Returns an ``Kernel2D`` by from a `PrimaryHDU` object which has been loaded via `astropy.fits` - - This assumes that the `header` of the `PrimaryHDU` contains an entry named `PIXSCALE` which gives the - pixel-scale of the array. - - For a full description of ``Kernel2D`` objects, including a description of the ``slim`` and ``native`` attribute - used by the API, see - the :meth:`Kernel2D class API documentation `. - - Parameters - ---------- - primary_hdu - The `PrimaryHDU` object which has already been loaded from a .fits file via `astropy.fits` and contains - the array data and the pixel-scale in the header with an entry named `PIXSCALE`. - origin - The (y,x) scaled units origin of the coordinate system. - - Examples - -------- - - .. code-block:: python - - from astropy.io import fits - import autoarray as aa - - primary_hdu = fits.open("path/to/file.fits") - - array_2d = aa.Kernel2D.from_primary_hdu( - primary_hdu=primary_hdu, - ) - """ - return cls.no_mask( - values=cls.flip_hdu_for_ds9(primary_hdu.data.astype("float")), - pixel_scales=primary_hdu.header["PIXSCALE"], - origin=origin, - ) - def rescaled_with_odd_dimensions_from( self, rescale_factor: float, normalize: bool = False ) -> "Kernel2D": diff --git a/autoarray/structures/arrays/uniform_1d.py b/autoarray/structures/arrays/uniform_1d.py index 944475b4b..cd6b56531 100644 --- a/autoarray/structures/arrays/uniform_1d.py +++ b/autoarray/structures/arrays/uniform_1d.py @@ -231,51 +231,6 @@ def from_fits( header=Header(header_sci_obj=header_sci_obj, header_hdu_obj=header_hdu_obj), ) - @classmethod - def from_primary_hdu( - cls, - primary_hdu: fits.PrimaryHDU, - origin: Tuple[float, float] = (0.0, 0.0), - ) -> "Array1D": - """ - Returns an ``Array1D`` by from a `PrimaryHDU` object which has been loaded via `astropy.fits` - - This assumes that the `header` of the `PrimaryHDU` contains an entry named `PIXSCALE` which gives the - pixel-scale of the array. - - For a full description of ``Array1D`` objects, including a description of the ``slim`` and ``native`` attribute - used by the API, see - the :meth:`Array1D class API documentation `. - - Parameters - ---------- - primary_hdu - The `PrimaryHDU` object which has already been loaded from a .fits file via `astropy.fits` and contains - the array data and the pixel-scale in the header with an entry named `PIXSCALE`. - origin - The (y,x) scaled units origin of the coordinate system. - - Examples - -------- - - .. code-block:: python - - from astropy.io import fits - import autoarray as aa - - primary_hdu = fits.open("path/to/file.fits") - - array_1d = aa.Array1D.from_primary_hdu( - primary_hdu=primary_hdu, - ) - """ - return cls.no_mask( - values=primary_hdu.data.astype("float"), - pixel_scales=primary_hdu.header["PIXSCALE"], - origin=origin, - header=Header(header_sci_obj=primary_hdu.header), - ) - @property def slim(self) -> "Array1D": """ diff --git a/autoarray/structures/arrays/uniform_2d.py b/autoarray/structures/arrays/uniform_2d.py index d29c43f81..e9dd15247 100644 --- a/autoarray/structures/arrays/uniform_2d.py +++ b/autoarray/structures/arrays/uniform_2d.py @@ -639,8 +639,8 @@ def output_to_fits(self, file_path: Union[Path, str], overwrite: bool = False): """ Output the array to a .fits file. - The `pixel_scale` is stored in the header as `PIXSCALE`, which is used by the `Array2D.from_primary_hdu` - method. + The `pixel_scales` are stored in the header as `PIXSCAY` and `PIXSCAX`, which are often used when loading + from fits. Parameters ---------- @@ -951,52 +951,6 @@ def from_fits( header=Header(header_sci_obj=header_sci_obj, header_hdu_obj=header_hdu_obj), ) - @classmethod - def from_primary_hdu( - cls, - primary_hdu: fits.PrimaryHDU, - origin: Tuple[float, float] = (0.0, 0.0), - ) -> "Array2D": - """ - Returns an ``Array2D`` by from a `PrimaryHDU` object which has been loaded via `astropy.fits` - - This assumes that the `header` of the `PrimaryHDU` contains an entry named `PIXSCALE` which gives the - pixel-scale of the array. - - For a full description of ``Array2D`` objects, including a description of the ``slim`` and ``native`` attribute - used by the API, see - the :meth:`Array2D class API documentation `. - - Parameters - ---------- - primary_hdu - The `PrimaryHDU` object which has already been loaded from a .fits file via `astropy.fits` and contains - the array data and the pixel-scale in the header with an entry named `PIXSCALE`. - origin - The (y,x) scaled units origin of the coordinate system. - - Examples - -------- - - .. code-block:: python - - from astropy.io import fits - import autoarray as aa - - primary_hdu = fits.open("path/to/file.fits") - - array_2d = aa.Array2D.from_primary_hdu( - primary_hdu=primary_hdu, - ) - """ - - return cls.no_mask( - values=cls.flip_hdu_for_ds9(primary_hdu.data.astype("float")), - pixel_scales=primary_hdu.header["PIXSCALE"], - origin=origin, - header=Header(header_sci_obj=primary_hdu.header), - ) - @classmethod def from_yx_and_values( cls, diff --git a/test_autoarray/mask/test_mask_1d.py b/test_autoarray/mask/test_mask_1d.py index faba516d6..41f02d3b7 100644 --- a/test_autoarray/mask/test_mask_1d.py +++ b/test_autoarray/mask/test_mask_1d.py @@ -84,26 +84,3 @@ def test__is_all_false(): mask = aa.Mask1D(mask=[True, True, False, False], pixel_scales=1.0) assert mask.is_all_false is False - - -def test__from_primary_hdu(): - file_path = os.path.join(test_data_path, "mask_out.fits") - - if os.path.exists(file_path): - os.remove(file_path) - - mask = np.array([True, False, True, False, False, True]).astype("int") - - aa.util.array_1d.numpy_array_1d_to_fits( - mask, file_path=file_path, header_dict={"PIXSCALE": 0.1} - ) - - primary_hdu = fits.open(file_path) - - mask_via_hdu = aa.Mask1D.from_primary_hdu( - primary_hdu=primary_hdu[0], - ) - - assert type(mask_via_hdu) == aa.Mask1D - assert (mask_via_hdu == mask).all() - assert mask_via_hdu.pixel_scales == (0.1,) diff --git a/test_autoarray/mask/test_mask_2d.py b/test_autoarray/mask/test_mask_2d.py index 3b80030e6..ed34755b6 100644 --- a/test_autoarray/mask/test_mask_2d.py +++ b/test_autoarray/mask/test_mask_2d.py @@ -386,29 +386,6 @@ def test__from_fits__with_resized_mask_shape(): assert mask.shape_native == (5, 5) -def test__from_primary_hdu(): - file_path = os.path.join(test_data_path, "array_out.fits") - - if os.path.exists(file_path): - os.remove(file_path) - - mask = np.array([[True, False, True], [False, False, True]]).astype("int") - - aa.util.array_2d.numpy_array_2d_to_fits( - mask, file_path=file_path, header_dict={"PIXSCALE": 0.1} - ) - - primary_hdu = fits.open(file_path) - - mask_via_hdu = aa.Mask2D.from_primary_hdu( - primary_hdu=primary_hdu[0], - ) - - assert type(mask_via_hdu) == aa.Mask2D - assert (mask_via_hdu == mask).all() - assert mask_via_hdu.pixel_scales == (0.1, 0.1) - - def test__mask__input_is_1d_mask__no_shape_native__raises_exception(): with pytest.raises(exc.MaskException): aa.Mask2D(mask=[False, False, True], pixel_scales=1.0) diff --git a/test_autoarray/structures/arrays/test_kernel_2d.py b/test_autoarray/structures/arrays/test_kernel_2d.py index 52a8d29b6..292d628f7 100644 --- a/test_autoarray/structures/arrays/test_kernel_2d.py +++ b/test_autoarray/structures/arrays/test_kernel_2d.py @@ -111,29 +111,6 @@ def test__from_gaussian(): ) -def test__from_primary_hdu(): - file_path = os.path.join(test_data_path, "array_out.fits") - - if os.path.exists(file_path): - os.remove(file_path) - - arr = np.array([[10.0, 30.0, 40.0], [92.0, 19.0, 20.0]]) - - aa.util.array_2d.numpy_array_2d_to_fits( - arr, file_path=file_path, header_dict={"PIXSCALE": 0.1} - ) - - primary_hdu = fits.open(file_path) - - array_2d = aa.Kernel2D.from_primary_hdu( - primary_hdu=primary_hdu[0], - ) - - assert type(array_2d) == aa.Kernel2D - assert (array_2d.native == arr).all() - assert array_2d.pixel_scales == (0.1, 0.1) - - def test__manual__normalize(): kernel_data = np.ones((3, 3)) / 9.0 kernel_2d = aa.Kernel2D.no_mask( diff --git a/test_autoarray/structures/arrays/test_uniform_1d.py b/test_autoarray/structures/arrays/test_uniform_1d.py index 54146478e..6cedff96a 100644 --- a/test_autoarray/structures/arrays/test_uniform_1d.py +++ b/test_autoarray/structures/arrays/test_uniform_1d.py @@ -145,29 +145,6 @@ def test__from_fits__loads_and_stores_header_info(): clean_fits(fits_path=fits_path) -def test__from_primary_hdu(): - file_path = os.path.join(test_data_path, "array_out.fits") - - if os.path.exists(file_path): - os.remove(file_path) - - arr = np.array([10.0, 30.0, 40.0, 92.0, 19.0, 20.0]) - - aa.util.array_1d.numpy_array_1d_to_fits( - arr, file_path=file_path, header_dict={"PIXSCALE": 0.1} - ) - - primary_hdu = fits.open(file_path) - - arr = aa.Array1D.from_primary_hdu( - primary_hdu=primary_hdu[0], - ) - - assert type(arr) == aa.Array1D - assert (arr.native == arr).all() - assert arr.pixel_scales == (0.1,) - - def test__output_to_fits(): arr = aa.Array1D.ones(shape_native=(3,), pixel_scales=1.0) diff --git a/test_autoarray/structures/arrays/test_uniform_2d.py b/test_autoarray/structures/arrays/test_uniform_2d.py index 040fcfae9..abf6a6abc 100644 --- a/test_autoarray/structures/arrays/test_uniform_2d.py +++ b/test_autoarray/structures/arrays/test_uniform_2d.py @@ -175,30 +175,6 @@ def test__from_fits__loads_and_stores_header_info(): assert array_2d.header.header_sci_obj["BITPIX"] == -64 assert array_2d.header.header_hdu_obj["BITPIX"] == -64 - -def test__from_primary_hdu(): - file_path = os.path.join(test_data_path, "array_out.fits") - - if os.path.exists(file_path): - os.remove(file_path) - - arr = np.array([[10.0, 30.0, 40.0], [92.0, 19.0, 20.0]]) - - aa.util.array_2d.numpy_array_2d_to_fits( - arr, file_path=file_path, header_dict={"PIXSCALE": 0.1} - ) - - primary_hdu = fits.open(file_path) - - array_2d = aa.Array2D.from_primary_hdu( - primary_hdu=primary_hdu[0], - ) - - assert type(array_2d) == aa.Array2D - assert (array_2d.native == arr).all() - assert array_2d.pixel_scales == (0.1, 0.1) - - def test__from_yx_and_values(): array_2d = aa.Array2D.from_yx_and_values( y=[0.5, 0.5, -0.5, -0.5], From 7802daf5af0db8be307214b8b07a281c8b0ff51a Mon Sep 17 00:00:00 2001 From: James Nightingale Date: Fri, 21 Mar 2025 13:44:36 +0000 Subject: [PATCH 05/13] ndarray_via_fits_from refactored --- autoarray/__init__.py | 1 + autoarray/abstract_ndarray.py | 9 ++++-- autoarray/dataset/interferometer/dataset.py | 8 ++---- autoarray/mask/mask_1d.py | 6 ++-- autoarray/mask/mask_2d.py | 6 ++-- autoarray/structures/arrays/uniform_1d.py | 18 ------------ autoarray/structures/arrays/uniform_2d.py | 28 ++----------------- autoarray/structures/grids/uniform_2d.py | 3 +- autoarray/structures/visibilities.py | 5 ++-- .../dataset/plot/test_imaging_plotters.py | 2 +- .../fit/plot/test_fit_imaging_plotters.py | 2 +- .../structures/arrays/test_array_2d_util.py | 8 +++--- .../plot/test_structure_plotters.py | 2 +- 13 files changed, 33 insertions(+), 65 deletions(-) diff --git a/autoarray/__init__.py b/autoarray/__init__.py index f91844712..4f677bf51 100644 --- a/autoarray/__init__.py +++ b/autoarray/__init__.py @@ -89,6 +89,7 @@ from .structures.visibilities import VisibilitiesNoiseMap from autoconf import conf +from autoconf.fitsable import ndarray_via_hdu_from conf.instance.register(__file__) diff --git a/autoarray/abstract_ndarray.py b/autoarray/abstract_ndarray.py index ff58d5718..09127af09 100644 --- a/autoarray/abstract_ndarray.py +++ b/autoarray/abstract_ndarray.py @@ -6,6 +6,8 @@ from abc import abstractmethod import numpy as np +from autoconf.fitsable import output_to_fits + from autoarray.numpy_wrapper import numpy as npw, register_pytree_node, Array from typing import TYPE_CHECKING @@ -280,8 +282,11 @@ def output_to_fits(self, file_path: str, overwrite: bool = False): overwrite If a file already exists at the path, if overwrite=True it is overwritten else an error is raised. """ - array_2d_util.numpy_array_2d_to_fits( - array_2d=self.native.array, file_path=file_path, overwrite=overwrite + output_to_fits( + values=self.native.array, + file_path=file_path, + overwrite=overwrite, + header_dict=self.mask.pixel_scale_header ) @property diff --git a/autoarray/dataset/interferometer/dataset.py b/autoarray/dataset/interferometer/dataset.py index 7a93e2cf3..cb6d8592c 100644 --- a/autoarray/dataset/interferometer/dataset.py +++ b/autoarray/dataset/interferometer/dataset.py @@ -2,9 +2,9 @@ import logging import numpy as np from pathlib import Path -from typing import Optional from autoconf import cached_property +from autoconf.fitsable import ndarray_via_fits_from, output_to_fits from autoarray.dataset.abstract.dataset import AbstractDataset from autoarray.dataset.interferometer.w_tilde import WTildeInterferometer @@ -14,8 +14,6 @@ from autoarray.structures.visibilities import Visibilities from autoarray.structures.visibilities import VisibilitiesNoiseMap -from autoarray.structures.arrays import array_2d_util - from autoarray.inversion.inversion.interferometer import inversion_interferometer_util logger = logging.getLogger(__name__) @@ -131,7 +129,7 @@ def from_fits( file_path=noise_map_path, hdu=noise_map_hdu ) - uv_wavelengths = array_2d_util.numpy_array_2d_via_fits_from( + uv_wavelengths = ndarray_via_fits_from( file_path=uv_wavelengths_path, hdu=uv_wavelengths_hdu ) @@ -271,7 +269,7 @@ def output_to_fits( self.noise_map.output_to_fits(file_path=noise_map_path, overwrite=overwrite) if self.uv_wavelengths is not None and uv_wavelengths_path is not None: - array_2d_util.numpy_array_2d_to_fits( + output_to_fits( array_2d=self.uv_wavelengths, file_path=uv_wavelengths_path, overwrite=overwrite, diff --git a/autoarray/mask/mask_1d.py b/autoarray/mask/mask_1d.py index 56bbb196a..c91ee545b 100644 --- a/autoarray/mask/mask_1d.py +++ b/autoarray/mask/mask_1d.py @@ -6,6 +6,7 @@ from pathlib import Path from typing import List, Tuple, Union +from autoconf.fitsable import output_to_fits from autoarray.mask.abstract_mask import Mask @@ -17,6 +18,7 @@ from autoarray import exc from autoarray import type as ty +from autoconf.fitsable import output_to_fits logging.basicConfig() logger = logging.getLogger(__name__) @@ -201,8 +203,8 @@ def output_to_fits(self, file_path: Union[Path, str], overwrite: bool = False): mask = Mask1D(mask=np.full(shape=(5,), fill_value=False)) mask.output_to_fits(file_path='/path/to/file/filename.fits', overwrite=True) """ - array_1d_util.numpy_array_1d_to_fits( - array_1d=self.astype("float"), + output_to_fits( + values=self.astype("float"), file_path=file_path, overwrite=overwrite, header_dict=self.pixel_scale_header, diff --git a/autoarray/mask/mask_2d.py b/autoarray/mask/mask_2d.py index af5b18818..2be06ce31 100644 --- a/autoarray/mask/mask_2d.py +++ b/autoarray/mask/mask_2d.py @@ -12,6 +12,7 @@ from autoarray.structures.arrays.uniform_2d import Array2D from autoconf import cached_property +from autoconf.fitsable import ndarray_via_fits_from, output_to_fits from autoarray.mask.abstract_mask import Mask @@ -640,7 +641,7 @@ def from_fits( """ pixel_scales = geometry_util.convert_pixel_scales_2d(pixel_scales=pixel_scales) - mask = array_2d_util.numpy_array_2d_via_fits_from(file_path=file_path, hdu=hdu) + mask = ndarray_via_fits_from(file_path=file_path, hdu=hdu) if invert: mask = np.invert(mask.astype("bool")) @@ -748,11 +749,12 @@ def output_to_fits(self, file_path, overwrite=False): mask = Mask2D(mask=np.full(shape=(5,5), fill_value=False)) mask.output_to_fits(file_path='/path/to/file/filename.fits', overwrite=True) """ - array_2d_util.numpy_array_2d_to_fits( + output_to_fits( array_2d=self.astype("float"), file_path=file_path, overwrite=overwrite, header_dict=self.pixel_scale_header, + ext_name="mask" ) @property diff --git a/autoarray/structures/arrays/uniform_1d.py b/autoarray/structures/arrays/uniform_1d.py index cd6b56531..b095b70f8 100644 --- a/autoarray/structures/arrays/uniform_1d.py +++ b/autoarray/structures/arrays/uniform_1d.py @@ -284,21 +284,3 @@ def hdu_for_output(self) -> fits.PrimaryHDU: return array_2d_util.hdu_for_output_from( array_2d=self.native, header_dict=self.pixel_scale_header ) - - def output_to_fits(self, file_path: Union[Path, str], overwrite: bool = False): - """ - Output the array to a .fits file. - - Parameters - ---------- - file_path - The output path of the file, including the filename and the `.fits` extension e.g. '/path/to/filename.fits' - overwrite - If a file already exists at the path, if overwrite=True it is overwritten else an error is raised. - """ - array_1d_util.numpy_array_1d_to_fits( - array_1d=self.native, - file_path=file_path, - overwrite=overwrite, - header_dict=self.pixel_scale_header, - ) diff --git a/autoarray/structures/arrays/uniform_2d.py b/autoarray/structures/arrays/uniform_2d.py index e9dd15247..d948c86cd 100644 --- a/autoarray/structures/arrays/uniform_2d.py +++ b/autoarray/structures/arrays/uniform_2d.py @@ -5,6 +5,7 @@ from typing import List, Optional, Tuple, Union from autoconf import conf +from autoconf.fitsable import ndarray_via_fits_from from autoarray.mask.mask_2d import Mask2D from autoarray.structures.abstract_structure import Structure @@ -227,10 +228,6 @@ def __init__( print(array_2d.slim) # masked 1D data representation. print(array_2d.native) # masked 2D data representation. - - # Output array to .fits file. - - array_2d.output_to_fits(file_path="/path/for/output") """ try: @@ -635,27 +632,6 @@ def hdu_for_output_from( return_as_primary=return_as_primary, ) - def output_to_fits(self, file_path: Union[Path, str], overwrite: bool = False): - """ - Output the array to a .fits file. - - The `pixel_scales` are stored in the header as `PIXSCAY` and `PIXSCAX`, which are often used when loading - from fits. - - Parameters - ---------- - file_path - The output path of the file, including the filename and the `.fits` extension e.g. '/path/to/filename.fits' - overwrite - If a file already exists at the path, if overwrite=True it is overwritten else an error is raised. - """ - array_2d_util.numpy_array_2d_to_fits( - array_2d=np.array(self.native), - file_path=file_path, - overwrite=overwrite, - header_dict=self.pixel_scale_header, - ) - class Array2D(AbstractArray2D): @classmethod @@ -937,7 +913,7 @@ def from_fits( pixel_scales=1.0, ) """ - array_2d = array_2d_util.numpy_array_2d_via_fits_from( + array_2d = ndarray_via_fits_from( file_path=file_path, hdu=hdu ) diff --git a/autoarray/structures/grids/uniform_2d.py b/autoarray/structures/grids/uniform_2d.py index e4cad24b0..c4fd72275 100644 --- a/autoarray/structures/grids/uniform_2d.py +++ b/autoarray/structures/grids/uniform_2d.py @@ -5,6 +5,7 @@ from autoconf import conf from autoconf import cached_property +from autoconf.fitsable import ndarray_via_fits_from from autoarray.mask.mask_2d import Mask2D from autoarray.structures.abstract_structure import Structure @@ -575,7 +576,7 @@ def from_fits( The mask whose masked pixels are used to setup the grid. """ - grid_2d = array_2d_util.numpy_array_2d_via_fits_from(file_path=file_path, hdu=0) + grid_2d = ndarray_via_fits_from(file_path=file_path, hdu=0) return Grid2D.no_mask( values=grid_2d, diff --git a/autoarray/structures/visibilities.py b/autoarray/structures/visibilities.py index a71bcaa21..9cc36d09e 100644 --- a/autoarray/structures/visibilities.py +++ b/autoarray/structures/visibilities.py @@ -8,6 +8,7 @@ from typing import List, Tuple, Union from autoconf import cached_property +from autoconf.fitsable import ndarray_via_fits_from, output_to_fits from autoarray.structures.abstract_structure import Structure from autoarray.structures.grids.irregular_2d import Grid2DIrregular @@ -133,7 +134,7 @@ def output_to_fits(self, file_path: Union[Path, str], overwrite: bool = False): overwrite If a file already exists at the path, if overwrite=True it is overwritten else an error is raised. """ - array_2d_util.numpy_array_2d_to_fits( + output_to_fits( array_2d=self.in_array, file_path=file_path, overwrite=overwrite ) @@ -223,7 +224,7 @@ def from_fits(cls, file_path: Union[Path, str], hdu: int) -> "Visibilities": hdu The Header-Data Unit of the .fits file the visibilitiy data is loaded from. """ - visibilities_1d = array_2d_util.numpy_array_2d_via_fits_from( + visibilities_1d = ndarray_via_fits_from( file_path=file_path, hdu=hdu ) return cls(visibilities=visibilities_1d) diff --git a/test_autoarray/dataset/plot/test_imaging_plotters.py b/test_autoarray/dataset/plot/test_imaging_plotters.py index d6c6673aa..cd7161b0c 100644 --- a/test_autoarray/dataset/plot/test_imaging_plotters.py +++ b/test_autoarray/dataset/plot/test_imaging_plotters.py @@ -77,7 +77,7 @@ def test__output_as_fits__correct_output_format( dataset_plotter.figures_2d(data=True, psf=True) - image_from_plot = aa.util.array_2d.numpy_array_2d_via_fits_from( + image_from_plot = aa.util.array_2d.ndarray_via_fits_from( file_path=path.join(plot_path, "data.fits"), hdu=0 ) diff --git a/test_autoarray/fit/plot/test_fit_imaging_plotters.py b/test_autoarray/fit/plot/test_fit_imaging_plotters.py index 24fe83b48..5432e5da1 100644 --- a/test_autoarray/fit/plot/test_fit_imaging_plotters.py +++ b/test_autoarray/fit/plot/test_fit_imaging_plotters.py @@ -83,7 +83,7 @@ def test__output_as_fits__correct_output_format( fit_plotter.figures_2d(data=True) - image_from_plot = aa.util.array_2d.numpy_array_2d_via_fits_from( + image_from_plot = aa.util.array_2d.ndarray_via_fits_from( file_path=path.join(plot_path, "data.fits"), hdu=0 ) diff --git a/test_autoarray/structures/arrays/test_array_2d_util.py b/test_autoarray/structures/arrays/test_array_2d_util.py index 15219619d..921bbda21 100644 --- a/test_autoarray/structures/arrays/test_array_2d_util.py +++ b/test_autoarray/structures/arrays/test_array_2d_util.py @@ -289,14 +289,14 @@ def test__resized_array_2d_from__padding_with_new_origin(): ).all() -def test__numpy_array_2d_via_fits_from(): - arr = util.array_2d.numpy_array_2d_via_fits_from( +def test__ndarray_via_fits_from(): + arr = util.array_2d.ndarray_via_fits_from( file_path=os.path.join(test_data_path, "3x3_ones.fits"), hdu=0 ) assert (arr == np.ones((3, 3))).all() - arr = util.array_2d.numpy_array_2d_via_fits_from( + arr = util.array_2d.ndarray_via_fits_from( file_path=os.path.join(test_data_path, "4x3_ones.fits"), hdu=0 ) @@ -313,7 +313,7 @@ def test__numpy_array_2d_to_fits(): util.array_2d.numpy_array_2d_to_fits(arr, file_path=file_path) - array_load = util.array_2d.numpy_array_2d_via_fits_from(file_path=file_path, hdu=0) + array_load = util.array_2d.ndarray_via_fits_from(file_path=file_path, hdu=0) assert (arr == array_load).all() diff --git a/test_autoarray/structures/plot/test_structure_plotters.py b/test_autoarray/structures/plot/test_structure_plotters.py index 8421be6cb..89254340d 100644 --- a/test_autoarray/structures/plot/test_structure_plotters.py +++ b/test_autoarray/structures/plot/test_structure_plotters.py @@ -105,7 +105,7 @@ def test__array__fits_files_output_correctly(array_2d_7x7, plot_path): array_plotter.figure_2d() - arr = aa.util.array_2d.numpy_array_2d_via_fits_from( + arr = aa.util.array_2d.ndarray_via_fits_from( file_path=path.join(plot_path, "array.fits"), hdu=0 ) From 77d8ec74cce4385c6ffa9eb86c15d93fede7dde1 Mon Sep 17 00:00:00 2001 From: James Nightingale Date: Fri, 21 Mar 2025 13:46:58 +0000 Subject: [PATCH 06/13] main __init__ import --- autoarray/__init__.py | 2 +- test_autoarray/dataset/plot/test_imaging_plotters.py | 2 +- test_autoarray/fit/plot/test_fit_imaging_plotters.py | 2 +- test_autoarray/structures/plot/test_structure_plotters.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/autoarray/__init__.py b/autoarray/__init__.py index 4f677bf51..7adcb3647 100644 --- a/autoarray/__init__.py +++ b/autoarray/__init__.py @@ -89,7 +89,7 @@ from .structures.visibilities import VisibilitiesNoiseMap from autoconf import conf -from autoconf.fitsable import ndarray_via_hdu_from +from autoconf.fitsable import ndarray_via_hdu_from, ndarray_via_fits_from conf.instance.register(__file__) diff --git a/test_autoarray/dataset/plot/test_imaging_plotters.py b/test_autoarray/dataset/plot/test_imaging_plotters.py index cd7161b0c..86c881325 100644 --- a/test_autoarray/dataset/plot/test_imaging_plotters.py +++ b/test_autoarray/dataset/plot/test_imaging_plotters.py @@ -77,7 +77,7 @@ def test__output_as_fits__correct_output_format( dataset_plotter.figures_2d(data=True, psf=True) - image_from_plot = aa.util.array_2d.ndarray_via_fits_from( + image_from_plot = aa.ndarray_via_fits_from( file_path=path.join(plot_path, "data.fits"), hdu=0 ) diff --git a/test_autoarray/fit/plot/test_fit_imaging_plotters.py b/test_autoarray/fit/plot/test_fit_imaging_plotters.py index 5432e5da1..4e5480ca7 100644 --- a/test_autoarray/fit/plot/test_fit_imaging_plotters.py +++ b/test_autoarray/fit/plot/test_fit_imaging_plotters.py @@ -83,7 +83,7 @@ def test__output_as_fits__correct_output_format( fit_plotter.figures_2d(data=True) - image_from_plot = aa.util.array_2d.ndarray_via_fits_from( + image_from_plot = aa.ndarray_via_fits_from( file_path=path.join(plot_path, "data.fits"), hdu=0 ) diff --git a/test_autoarray/structures/plot/test_structure_plotters.py b/test_autoarray/structures/plot/test_structure_plotters.py index 89254340d..b10de853b 100644 --- a/test_autoarray/structures/plot/test_structure_plotters.py +++ b/test_autoarray/structures/plot/test_structure_plotters.py @@ -105,7 +105,7 @@ def test__array__fits_files_output_correctly(array_2d_7x7, plot_path): array_plotter.figure_2d() - arr = aa.util.array_2d.ndarray_via_fits_from( + arr = aa.ndarray_via_fits_from( file_path=path.join(plot_path, "array.fits"), hdu=0 ) From 538aa09d313292c7fd3dcbe85b64d5d58ff8f095 Mon Sep 17 00:00:00 2001 From: James Nightingale Date: Fri, 21 Mar 2025 13:52:56 +0000 Subject: [PATCH 07/13] update output_to_Fits kwarg --- autoarray/__init__.py | 2 +- autoarray/abstract_ndarray.py | 1 - autoarray/dataset/interferometer/dataset.py | 2 +- autoarray/mask/mask_1d.py | 1 - autoarray/mask/mask_2d.py | 2 +- autoarray/structures/arrays/kernel_2d.py | 6 ++++-- autoarray/structures/arrays/uniform_1d.py | 8 +++++--- autoarray/structures/arrays/uniform_2d.py | 6 +++--- autoarray/structures/visibilities.py | 2 +- test_autoarray/mask/test_mask_2d.py | 2 +- .../arrays/files/array/output_test/array.fits | Bin 5760 -> 5760 bytes .../structures/arrays/files/array_out.fits | Bin 5760 -> 0 bytes .../structures/arrays/test_array_1d_util.py | 2 +- .../structures/arrays/test_array_2d_util.py | 6 ++++-- .../structures/arrays/test_uniform_1d.py | 2 +- test_autoarray/structures/files/kernel.fits | Bin 5760 -> 5760 bytes 16 files changed, 23 insertions(+), 19 deletions(-) delete mode 100644 test_autoarray/structures/arrays/files/array_out.fits diff --git a/autoarray/__init__.py b/autoarray/__init__.py index 7adcb3647..3ca7fe7e1 100644 --- a/autoarray/__init__.py +++ b/autoarray/__init__.py @@ -89,7 +89,7 @@ from .structures.visibilities import VisibilitiesNoiseMap from autoconf import conf -from autoconf.fitsable import ndarray_via_hdu_from, ndarray_via_fits_from +from autoconf.fitsable import ndarray_via_hdu_from, ndarray_via_fits_from, header_obj_from conf.instance.register(__file__) diff --git a/autoarray/abstract_ndarray.py b/autoarray/abstract_ndarray.py index 09127af09..8da2cad61 100644 --- a/autoarray/abstract_ndarray.py +++ b/autoarray/abstract_ndarray.py @@ -15,7 +15,6 @@ if TYPE_CHECKING: from autoarray.structures.abstract_structure import Structure -from autoarray.structures.arrays import array_2d_util from autoconf import conf diff --git a/autoarray/dataset/interferometer/dataset.py b/autoarray/dataset/interferometer/dataset.py index cb6d8592c..21dd600a6 100644 --- a/autoarray/dataset/interferometer/dataset.py +++ b/autoarray/dataset/interferometer/dataset.py @@ -270,7 +270,7 @@ def output_to_fits( if self.uv_wavelengths is not None and uv_wavelengths_path is not None: output_to_fits( - array_2d=self.uv_wavelengths, + values=self.uv_wavelengths, file_path=uv_wavelengths_path, overwrite=overwrite, ) diff --git a/autoarray/mask/mask_1d.py b/autoarray/mask/mask_1d.py index c91ee545b..9a2d4b8f8 100644 --- a/autoarray/mask/mask_1d.py +++ b/autoarray/mask/mask_1d.py @@ -18,7 +18,6 @@ from autoarray import exc from autoarray import type as ty -from autoconf.fitsable import output_to_fits logging.basicConfig() logger = logging.getLogger(__name__) diff --git a/autoarray/mask/mask_2d.py b/autoarray/mask/mask_2d.py index 2be06ce31..eda5e1efb 100644 --- a/autoarray/mask/mask_2d.py +++ b/autoarray/mask/mask_2d.py @@ -750,7 +750,7 @@ def output_to_fits(self, file_path, overwrite=False): mask.output_to_fits(file_path='/path/to/file/filename.fits', overwrite=True) """ output_to_fits( - array_2d=self.astype("float"), + values=self.astype("float"), file_path=file_path, overwrite=overwrite, header_dict=self.pixel_scale_header, diff --git a/autoarray/structures/arrays/kernel_2d.py b/autoarray/structures/arrays/kernel_2d.py index 435011fd9..1e7650b0a 100644 --- a/autoarray/structures/arrays/kernel_2d.py +++ b/autoarray/structures/arrays/kernel_2d.py @@ -5,6 +5,8 @@ from pathlib import Path from typing import List, Tuple, Union +from autoconf.fitsable import header_obj_from + from autoarray.mask.mask_2d import Mask2D from autoarray.structures.arrays.uniform_2d import AbstractArray2D from autoarray.structures.arrays.uniform_2d import Array2D @@ -345,8 +347,8 @@ def from_fits( file_path=file_path, hdu=hdu, pixel_scales=pixel_scales, origin=origin ) - header_sci_obj = array_2d_util.header_obj_from(file_path=file_path, hdu=0) - header_hdu_obj = array_2d_util.header_obj_from(file_path=file_path, hdu=hdu) + header_sci_obj = header_obj_from(file_path=file_path, hdu=0) + header_hdu_obj = header_obj_from(file_path=file_path, hdu=hdu) return Kernel2D( values=array[:], diff --git a/autoarray/structures/arrays/uniform_1d.py b/autoarray/structures/arrays/uniform_1d.py index b095b70f8..834048e91 100644 --- a/autoarray/structures/arrays/uniform_1d.py +++ b/autoarray/structures/arrays/uniform_1d.py @@ -3,6 +3,8 @@ from pathlib import Path from typing import Optional, Union, Tuple, List +from autoconf.fitsable import ndarray_via_fits_from, header_obj_from + from autoarray.structures.header import Header from autoarray.structures.abstract_structure import Structure @@ -215,12 +217,12 @@ def from_fits( origin The (x,) scaled units origin of the coordinate system. """ - array_1d = array_1d_util.numpy_array_1d_via_fits_from( + array_1d = ndarray_via_fits_from( file_path=file_path, hdu=hdu ) - header_sci_obj = array_2d_util.header_obj_from(file_path=file_path, hdu=0) - header_hdu_obj = array_2d_util.header_obj_from(file_path=file_path, hdu=hdu) + header_sci_obj = header_obj_from(file_path=file_path, hdu=0) + header_hdu_obj = header_obj_from(file_path=file_path, hdu=hdu) return cls.no_mask( values=array_1d.astype( diff --git a/autoarray/structures/arrays/uniform_2d.py b/autoarray/structures/arrays/uniform_2d.py index d948c86cd..6f8238d8a 100644 --- a/autoarray/structures/arrays/uniform_2d.py +++ b/autoarray/structures/arrays/uniform_2d.py @@ -5,7 +5,7 @@ from typing import List, Optional, Tuple, Union from autoconf import conf -from autoconf.fitsable import ndarray_via_fits_from +from autoconf.fitsable import ndarray_via_fits_from, header_obj_from from autoarray.mask.mask_2d import Mask2D from autoarray.structures.abstract_structure import Structure @@ -917,8 +917,8 @@ def from_fits( file_path=file_path, hdu=hdu ) - header_sci_obj = array_2d_util.header_obj_from(file_path=file_path, hdu=0) - header_hdu_obj = array_2d_util.header_obj_from(file_path=file_path, hdu=hdu) + header_sci_obj = header_obj_from(file_path=file_path, hdu=0) + header_hdu_obj = header_obj_from(file_path=file_path, hdu=hdu) return cls.no_mask( values=array_2d, diff --git a/autoarray/structures/visibilities.py b/autoarray/structures/visibilities.py index 9cc36d09e..55ad651b6 100644 --- a/autoarray/structures/visibilities.py +++ b/autoarray/structures/visibilities.py @@ -135,7 +135,7 @@ def output_to_fits(self, file_path: Union[Path, str], overwrite: bool = False): If a file already exists at the path, if overwrite=True it is overwritten else an error is raised. """ output_to_fits( - array_2d=self.in_array, file_path=file_path, overwrite=overwrite + values=self.in_array, file_path=file_path, overwrite=overwrite ) @property diff --git a/test_autoarray/mask/test_mask_2d.py b/test_autoarray/mask/test_mask_2d.py index ed34755b6..7fb5fcc30 100644 --- a/test_autoarray/mask/test_mask_2d.py +++ b/test_autoarray/mask/test_mask_2d.py @@ -359,7 +359,7 @@ def test__from_fits__output_to_fits(): assert mask.pixel_scales == (1.0, 1.0) assert mask.origin == (2.0, 2.0) - header = aa.util.array_2d.header_obj_from( + header = aa.header_obj_from( file_path=path.join(test_data_path, "mask.fits"), hdu=0 ) diff --git a/test_autoarray/structures/arrays/files/array/output_test/array.fits b/test_autoarray/structures/arrays/files/array/output_test/array.fits index 97c9a0707f9c5c472bf5ac3a1811cc6f31384be9..712bb607d50595518eed059269b771a085467a17 100644 GIT binary patch delta 31 ncmZqBZP48?jgcu*Ve$+{kI54l*_a{}HqT&;;N0xM?Z6EHpB@P- delta 24 fcmZqBZP48?jgiU6b@L0x2+qj?OaYr6xE;6wX*mb5 diff --git a/test_autoarray/structures/arrays/files/array_out.fits b/test_autoarray/structures/arrays/files/array_out.fits deleted file mode 100644 index 7f91ac1f39aaae4f622e85ccd3370fe904321d32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5760 zcmeH@O$&lR5Qh6Ha|k;1VM(__D}#lKKp`PIY-!-Z-B|75zi%-^4=d%lcMki`4#UIl z Date: Fri, 21 Mar 2025 13:58:08 +0000 Subject: [PATCH 08/13] remoe fitsable unit tests to autconf --- .../structures/arrays/test_array_1d_util.py | 32 ---------- .../structures/arrays/test_array_2d_util.py | 58 ----------------- .../structures/test_flip_for_ds9.py | 63 ------------------- 3 files changed, 153 deletions(-) delete mode 100644 test_autoarray/structures/test_flip_for_ds9.py diff --git a/test_autoarray/structures/arrays/test_array_1d_util.py b/test_autoarray/structures/arrays/test_array_1d_util.py index 2739e682d..93a3d80dc 100644 --- a/test_autoarray/structures/arrays/test_array_1d_util.py +++ b/test_autoarray/structures/arrays/test_array_1d_util.py @@ -56,35 +56,3 @@ def test__array_1d_native_from(): ) assert (array_1d_native == np.array([1.0, 2.0, 0.0, 0.0, 3.0, 4.0])).all() - - -def test__numpy_array_1d_via_fits_from(): - arr = aa.util.array_1d.numpy_array_1d_via_fits_from( - file_path=path.join(test_data_path, "3_ones.fits"), hdu=0 - ) - - assert (arr == np.ones((3))).all() - - -def test__numpy_array_1d_to_fits__output_and_load(): - file_path = path.join(test_data_path, "array_out.fits") - - if path.exists(file_path): - os.remove(file_path) - - arr = np.array([10.0, 30.0, 40.0, 92.0, 19.0, 20.0]) - - aa.util.array_1d.numpy_array_1d_to_fits( - arr, file_path=file_path, header_dict={"A": 1} - ) - - array_load = aa.util.array_1d.numpy_array_1d_via_fits_from( - file_path=file_path, - hdu=0, - ) - - assert (arr == array_load).all() - - header_load = aa.header_obj_from(file_path=file_path, hdu=0) - - assert header_load["A"] == 1 diff --git a/test_autoarray/structures/arrays/test_array_2d_util.py b/test_autoarray/structures/arrays/test_array_2d_util.py index 400c8eb98..7ce05b17b 100644 --- a/test_autoarray/structures/arrays/test_array_2d_util.py +++ b/test_autoarray/structures/arrays/test_array_2d_util.py @@ -2,11 +2,7 @@ import os import numpy as np -import pytest -from astropy.io import fits - -import autoarray as aa test_data_path = os.path.join( "{}".format(os.path.dirname(os.path.realpath(__file__))), "files" @@ -290,60 +286,6 @@ def test__resized_array_2d_from__padding_with_new_origin(): ) ).all() - -def test__ndarray_via_fits_from(): - arr = util.array_2d.ndarray_via_fits_from( - file_path=os.path.join(test_data_path, "3x3_ones.fits"), hdu=0 - ) - - assert (arr == np.ones((3, 3))).all() - - arr = util.array_2d.ndarray_via_fits_from( - file_path=os.path.join(test_data_path, "4x3_ones.fits"), hdu=0 - ) - - assert (arr == np.ones((4, 3))).all() - - -def test__numpy_array_2d_to_fits(): - file_path = os.path.join(test_data_path, "array_out.fits") - - if os.path.exists(file_path): - os.remove(file_path) - - arr = np.array([[10.0, 30.0, 40.0], [92.0, 19.0, 20.0]]) - - util.array_2d.numpy_array_2d_to_fits(arr, file_path=file_path) - - array_load = util.array_2d.ndarray_via_fits_from(file_path=file_path, hdu=0) - - assert (arr == array_load).all() - - -def test__numpy_array_2d_to_fits__header_dict(): - file_path = os.path.join(test_data_path, "array_out.fits") - - if os.path.exists(file_path): - os.remove(file_path) - - arr = np.array([[10.0, 30.0, 40.0], [92.0, 19.0, 20.0]]) - - util.array_2d.numpy_array_2d_to_fits(arr, file_path=file_path, header_dict={"A": 1}) - - header = aa.header_obj_from(file_path=file_path, hdu=0) - - assert header["A"] == 1 - - -def test__header_obj_from(): - header_obj = aa.header_obj_from( - file_path=os.path.join(test_data_path, "3x3_ones.fits"), hdu=0 - ) - - assert isinstance(header_obj, fits.header.Header) - assert header_obj["BITPIX"] == -64 - - def test__replace_noise_map_2d_values_where_image_2d_values_are_negative(): image_2d = np.ones(shape=(2, 2)) diff --git a/test_autoarray/structures/test_flip_for_ds9.py b/test_autoarray/structures/test_flip_for_ds9.py deleted file mode 100644 index ce48d3345..000000000 --- a/test_autoarray/structures/test_flip_for_ds9.py +++ /dev/null @@ -1,63 +0,0 @@ -from astropy.io import fits - -import os -from os import path -import numpy as np -from autoconf import conf -import autoarray as aa - -test_path = "{}".format(path.dirname(path.realpath(__file__))) - - -def create_fits(fits_path, array): - if path.exists(fits_path): - os.remove(fits_path) - - hdu_list = fits.HDUList() - - hdu_list.append(fits.ImageHDU(array)) - - hdu_list.writeto(f"{fits_path}") - - -def test__from_fits__all_imaging_data_structures_are_flipped_for_ds9(): - conf.instance.push(new_path=path.join(test_path, "files", "config_flip")) - - fits_path = path.join("{}".format(path.dirname(path.realpath(__file__))), "files") - - arr = np.array([[1.0, 0.0], [0.0, 0.0]]) - array_path = path.join(fits_path, "array.fits") - create_fits(fits_path=array_path, array=arr) - - arr = aa.Array2D.from_fits(file_path=array_path, hdu=0, pixel_scales=1.0) - assert (arr.native == np.array([[0.0, 0.0], [1.0, 0.0]])).all() - - arr.output_to_fits(file_path=array_path, overwrite=True) - - hdu_list = fits.open(array_path) - arr = np.array(hdu_list[0].data).astype("float64") - assert (arr == np.array([[1.0, 0.0], [0.0, 0.0]])).all() - - array = np.array([[2.0, 0.0], [0.0, 0.0]]) - array_path = path.join(fits_path, "array.fits") - create_fits(fits_path=array_path, array=array) - - array = aa.Array2D.from_fits(file_path=array_path, hdu=0, pixel_scales=1.0) - assert (array.native == np.array([[0.0, 0.0], [2.0, 0.0]])).all() - - array.output_to_fits(file_path=array_path, overwrite=True) - hdu_list = fits.open(array_path) - array = np.array(hdu_list[0].data).astype("float64") - assert (array == np.array([[2.0, 0.0], [0.0, 0.0]])).all() - - kernel = np.array([[3.0, 0.0], [0.0, 0.0]]) - kernel_path = path.join(fits_path, "kernel.fits") - create_fits(fits_path=kernel_path, array=kernel) - - kernel = aa.Kernel2D.from_fits(file_path=kernel_path, hdu=0, pixel_scales=1.0) - assert (kernel.native == np.array([[0.0, 0.0], [3.0, 0.0]])).all() - - kernel.output_to_fits(file_path=kernel_path, overwrite=True) - hdu_list = fits.open(kernel_path) - kernel = np.array(hdu_list[0].data).astype("float64") - assert (kernel == np.array([[3.0, 0.0], [0.0, 0.0]])).all() From 34ae6ffc47b1a5580b71a08ed11bc1a561053bef Mon Sep 17 00:00:00 2001 From: James Nightingale Date: Fri, 21 Mar 2025 16:00:43 +0000 Subject: [PATCH 09/13] pixel scale dict works in 1d and 2d --- autoarray/__init__.py | 6 +- autoarray/mask/abstract_mask.py | 4 -- autoarray/mask/mask_1d.py | 18 ++++- autoarray/mask/mask_2d.py | 20 +++++- autoarray/plot/multi_plotters.py | 68 ------------------- autoarray/plot/wrap/base/output.py | 11 --- test_autoarray/mask/test_mask_2d.py | 2 +- .../structures/arrays/test_array_2d_util.py | 1 + .../structures/arrays/test_uniform_1d.py | 2 +- .../structures/arrays/test_uniform_2d.py | 2 +- 10 files changed, 44 insertions(+), 90 deletions(-) diff --git a/autoarray/__init__.py b/autoarray/__init__.py index 3ca7fe7e1..18518971d 100644 --- a/autoarray/__init__.py +++ b/autoarray/__init__.py @@ -89,7 +89,11 @@ from .structures.visibilities import VisibilitiesNoiseMap from autoconf import conf -from autoconf.fitsable import ndarray_via_hdu_from, ndarray_via_fits_from, header_obj_from +from autoconf.fitsable import ndarray_via_hdu_from +from autoconf.fitsable import ndarray_via_fits_from +from autoconf.fitsable import header_obj_from +from autoconf.fitsable import output_to_fits +from autoconf.fitsable import hdu_list_for_output_from conf.instance.register(__file__) diff --git a/autoarray/mask/abstract_mask.py b/autoarray/mask/abstract_mask.py index ab33a7e11..d31b59621 100644 --- a/autoarray/mask/abstract_mask.py +++ b/autoarray/mask/abstract_mask.py @@ -87,10 +87,6 @@ def pixel_scale_header(self) -> Dict: ------- A dictionary containing the pixel scale of the mask, which can be output to a .fits file. """ - return { - "PIXSCAY": self.pixel_scales[0], - "PIXSCAX": self.pixel_scales[1], - } @property def dimensions(self) -> int: diff --git a/autoarray/mask/mask_1d.py b/autoarray/mask/mask_1d.py index 9a2d4b8f8..28e895dbe 100644 --- a/autoarray/mask/mask_1d.py +++ b/autoarray/mask/mask_1d.py @@ -4,7 +4,7 @@ import logging import numpy as np from pathlib import Path -from typing import List, Tuple, Union +from typing import Dict, List, Tuple, Union from autoconf.fitsable import output_to_fits @@ -163,6 +163,22 @@ def shape_native(self) -> Tuple[int]: def shape_slim(self) -> Tuple[int]: return self.shape + @property + def pixel_scale_header(self) -> Dict: + """ + Returns the pixel scales of the mask as a header dictionary, which can be written to a .fits file. + + A 2D mask has different pixel scale variables for each dimension, the header therefore contain both pixel + scales as separate y and x entries. + + Returns + ------- + A dictionary containing the pixel scale of the mask, which can be output to a .fits file. + """ + return { + "PIXSCA": self.pixel_scales[0], + } + @property def hdu_for_output(self) -> fits.PrimaryHDU: """ diff --git a/autoarray/mask/mask_2d.py b/autoarray/mask/mask_2d.py index eda5e1efb..27234acb1 100644 --- a/autoarray/mask/mask_2d.py +++ b/autoarray/mask/mask_2d.py @@ -1,10 +1,9 @@ from __future__ import annotations from astropy.io import fits -import copy import logging import numpy as np from pathlib import Path -from typing import TYPE_CHECKING, List, Tuple, Union +from typing import TYPE_CHECKING, Dict, List, Tuple, Union from autoarray.structures.abstract_structure import Structure @@ -706,6 +705,23 @@ def unmasked_blurred_array_from(self, padded_array, psf, image_shape) -> Array2D padded_array=blurred_image, image_shape=image_shape ) + @property + def pixel_scale_header(self) -> Dict: + """ + Returns the pixel scales of the mask as a header dictionary, which can be written to a .fits file. + + A 2D mask has different pixel scale variables for each dimension, the header therefore contain both pixel + scales as separate y and x entries. + + Returns + ------- + A dictionary containing the pixel scale of the mask, which can be output to a .fits file. + """ + return { + "PIXSCAY": self.pixel_scales[0], + "PIXSCAX": self.pixel_scales[1], + } + @property def hdu_for_output(self) -> fits.PrimaryHDU: """ diff --git a/autoarray/plot/multi_plotters.py b/autoarray/plot/multi_plotters.py index a58d08c02..f39b2a21f 100644 --- a/autoarray/plot/multi_plotters.py +++ b/autoarray/plot/multi_plotters.py @@ -270,74 +270,6 @@ def output_subplot(self, filename_suffix: str = ""): ) 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 945f91abd..73a5f40e4 100644 --- a/autoarray/plot/wrap/base/output.py +++ b/autoarray/plot/wrap/base/output.py @@ -153,17 +153,6 @@ 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/test_autoarray/mask/test_mask_2d.py b/test_autoarray/mask/test_mask_2d.py index 7fb5fcc30..3e8ad8b87 100644 --- a/test_autoarray/mask/test_mask_2d.py +++ b/test_autoarray/mask/test_mask_2d.py @@ -363,7 +363,7 @@ def test__from_fits__output_to_fits(): file_path=path.join(test_data_path, "mask.fits"), hdu=0 ) - assert header["PIXSCALE"] == 1.0 + assert header["PIXSCAY"] == 1.0 def test__from_fits__with_resized_mask_shape(): diff --git a/test_autoarray/structures/arrays/test_array_2d_util.py b/test_autoarray/structures/arrays/test_array_2d_util.py index 7ce05b17b..9468dd510 100644 --- a/test_autoarray/structures/arrays/test_array_2d_util.py +++ b/test_autoarray/structures/arrays/test_array_2d_util.py @@ -286,6 +286,7 @@ def test__resized_array_2d_from__padding_with_new_origin(): ) ).all() + def test__replace_noise_map_2d_values_where_image_2d_values_are_negative(): image_2d = np.ones(shape=(2, 2)) diff --git a/test_autoarray/structures/arrays/test_uniform_1d.py b/test_autoarray/structures/arrays/test_uniform_1d.py index a9aa18456..578e064b4 100644 --- a/test_autoarray/structures/arrays/test_uniform_1d.py +++ b/test_autoarray/structures/arrays/test_uniform_1d.py @@ -165,7 +165,7 @@ def test__output_to_fits(): file_path=path.join(test_data_path, "array.fits"), hdu=0 ) - assert header_load["PIXSCALE"] == 1.0 + assert header_load["PIXSCA"] == 1.0 def test__recursive_shape_storage(): diff --git a/test_autoarray/structures/arrays/test_uniform_2d.py b/test_autoarray/structures/arrays/test_uniform_2d.py index abf6a6abc..b433f846f 100644 --- a/test_autoarray/structures/arrays/test_uniform_2d.py +++ b/test_autoarray/structures/arrays/test_uniform_2d.py @@ -224,7 +224,7 @@ def test__output_to_fits(): ) assert (array_from_fits.native == np.ones((3, 3))).all() - assert array_from_fits.header.header_sci_obj["PIXSCALE"] == 1.0 + assert array_from_fits.header.header_sci_obj["PIXSCAY"] == 1.0 def test__manual_native__exception_raised_if_input_array_is_2d_and_not_shape_of_mask(): From 482ce26061392da55777067a5fc4f8106bfdb9b6 Mon Sep 17 00:00:00 2001 From: James Nightingale Date: Fri, 21 Mar 2025 16:08:40 +0000 Subject: [PATCH 10/13] remoe output_to_Fits from mask to abstract mask --- autoarray/mask/abstract_mask.py | 37 ++++++++++++++-- autoarray/mask/mask_1d.py | 51 +-------------------- autoarray/mask/mask_2d.py | 54 +---------------------- autoarray/structures/arrays/uniform_1d.py | 18 -------- autoarray/structures/arrays/uniform_2d.py | 22 --------- autoarray/structures/visibilities.py | 20 --------- 6 files changed, 35 insertions(+), 167 deletions(-) diff --git a/autoarray/mask/abstract_mask.py b/autoarray/mask/abstract_mask.py index d31b59621..717ce9e94 100644 --- a/autoarray/mask/abstract_mask.py +++ b/autoarray/mask/abstract_mask.py @@ -6,9 +6,10 @@ from pathlib import Path from typing import Dict, Union +from autoconf.fitsable import output_to_fits + from autoarray.abstract_ndarray import AbstractNDArray -from autoarray import exc from autoarray import type as ty logging.basicConfig() @@ -92,10 +93,38 @@ def pixel_scale_header(self) -> Dict: def dimensions(self) -> int: return len(self.shape) - def output_to_fits(self, file_path: Union[Path, str], overwrite: bool = False): - """ - Overwrite with method to output the mask to a `.fits` file. + def output_to_fits(self, file_path, overwrite=False): """ + Write the Mask to a .fits file. + + Before outputting a 2D NumPy array mask, the array may be flipped upside-down using np.flipud depending on + the project config files. This is for Astronomy projects so that structures appear the same orientation + as `.fits` files loaded in DS9. + + Parameters + ---------- + file_path + The full path of the file that is output, including the file name and `.fits` extension. + overwrite + If `True` and a file already exists with the input file_path the .fits file is overwritten. If `False`, an + error is raised. + + Returns + ------- + None + + Examples + -------- + mask = Mask2D(mask=np.full(shape=(5,5), fill_value=False)) + mask.output_to_fits(file_path='/path/to/file/filename.fits', overwrite=True) + """ + output_to_fits( + values=self.astype("float"), + file_path=file_path, + overwrite=overwrite, + header_dict=self.pixel_scale_header, + ext_name="mask" + ) @property def pixels_in_mask(self) -> int: diff --git a/autoarray/mask/mask_1d.py b/autoarray/mask/mask_1d.py index 28e895dbe..ab64b8657 100644 --- a/autoarray/mask/mask_1d.py +++ b/autoarray/mask/mask_1d.py @@ -1,13 +1,10 @@ from __future__ import annotations -from astropy.io import fits import logging import numpy as np from pathlib import Path from typing import Dict, List, Tuple, Union -from autoconf.fitsable import output_to_fits - from autoarray.mask.abstract_mask import Mask from autoarray.mask.derive.grid_1d import DeriveGrid1D @@ -177,50 +174,4 @@ def pixel_scale_header(self) -> Dict: """ return { "PIXSCA": self.pixel_scales[0], - } - - @property - def hdu_for_output(self) -> fits.PrimaryHDU: - """ - The mask as a HDU object, which can be output to a .fits file. - - The header of the HDU is used to store the `pixel_scale` of the array, which is used by the `Array1D.from_hdu`. - - This method is used in other projects (E.g. PyAutoGalaxy, PyAutoLens) to conveniently output the array to .fits - files. - - Returns - ------- - The HDU containing the data and its header which can then be written to .fits. - """ - return array_1d_util.hdu_for_output_from( - array_1d=self.astype("float"), header_dict=self.pixel_scale_header - ) - - def output_to_fits(self, file_path: Union[Path, str], overwrite: bool = False): - """ - Write the 1D mask to a .fits file. - - Parameters - ---------- - file_path - The full path of the file that is output, including the file name and .fits extension. - overwrite - If `True` and a file already exists with the input file_path the .fits file is overwritten. If `False`, - an error is raised. - - Returns - ------- - None - - Examples - -------- - mask = Mask1D(mask=np.full(shape=(5,), fill_value=False)) - mask.output_to_fits(file_path='/path/to/file/filename.fits', overwrite=True) - """ - output_to_fits( - values=self.astype("float"), - file_path=file_path, - overwrite=overwrite, - header_dict=self.pixel_scale_header, - ) + } \ No newline at end of file diff --git a/autoarray/mask/mask_2d.py b/autoarray/mask/mask_2d.py index 27234acb1..ec7585702 100644 --- a/autoarray/mask/mask_2d.py +++ b/autoarray/mask/mask_2d.py @@ -1,5 +1,4 @@ from __future__ import annotations -from astropy.io import fits import logging import numpy as np from pathlib import Path @@ -11,7 +10,7 @@ from autoarray.structures.arrays.uniform_2d import Array2D from autoconf import cached_property -from autoconf.fitsable import ndarray_via_fits_from, output_to_fits +from autoconf.fitsable import ndarray_via_fits_from from autoarray.mask.abstract_mask import Mask @@ -722,57 +721,6 @@ def pixel_scale_header(self) -> Dict: "PIXSCAX": self.pixel_scales[1], } - @property - def hdu_for_output(self) -> fits.PrimaryHDU: - """ - The mask as a HDU object, which can be output to a .fits file. - - The header of the HDU is used to store the `pixel_scale` of the array, which is used by the `Array2D.from_hdu`. - - This method is used in other projects (E.g. PyAutoGalaxy, PyAutoLens) to conveniently output the array to .fits - files. - - Returns - ------- - The HDU containing the data and its header which can then be written to .fits. - """ - return array_2d_util.hdu_for_output_from( - array_2d=self.astype("float"), header_dict=self.pixel_scale_header - ) - - def output_to_fits(self, file_path, overwrite=False): - """ - Write the 2D Mask to a .fits file. - - Before outputting a NumPy array, the array may be flipped upside-down using np.flipud depending on the project - config files. This is for Astronomy projects so that structures appear the same orientation as `.fits` files - loaded in DS9. - - Parameters - ---------- - file_path - The full path of the file that is output, including the file name and `.fits` extension. - overwrite - If `True` and a file already exists with the input file_path the .fits file is overwritten. If `False`, an - error is raised. - - Returns - ------- - None - - Examples - -------- - mask = Mask2D(mask=np.full(shape=(5,5), fill_value=False)) - mask.output_to_fits(file_path='/path/to/file/filename.fits', overwrite=True) - """ - output_to_fits( - values=self.astype("float"), - file_path=file_path, - overwrite=overwrite, - header_dict=self.pixel_scale_header, - ext_name="mask" - ) - @property def mask_centre(self) -> Tuple[float, float]: grid = grid_2d_util.grid_2d_slim_via_mask_from( diff --git a/autoarray/structures/arrays/uniform_1d.py b/autoarray/structures/arrays/uniform_1d.py index 834048e91..de460885b 100644 --- a/autoarray/structures/arrays/uniform_1d.py +++ b/autoarray/structures/arrays/uniform_1d.py @@ -268,21 +268,3 @@ def grid_radial(self) -> Grid1D: shape_native=self.shape_native, pixel_scales=self.pixel_scales, ) - - @property - def hdu_for_output(self) -> fits.PrimaryHDU: - """ - The array as an HDU object, which can be output to a .fits file. - - The header of the HDU is used to store the `pixel_scale` of the array, which is used by the `Array1D.from_hdu`. - - This method is used in other projects (E.g. PyAutoGalaxy, PyAutoLens) to conveniently output the array to .fits - files. - - Returns - ------- - The HDU containing the data and its header which can then be written to .fits. - """ - return array_2d_util.hdu_for_output_from( - array_2d=self.native, header_dict=self.pixel_scale_header - ) diff --git a/autoarray/structures/arrays/uniform_2d.py b/autoarray/structures/arrays/uniform_2d.py index 6f8238d8a..fd7041c9b 100644 --- a/autoarray/structures/arrays/uniform_2d.py +++ b/autoarray/structures/arrays/uniform_2d.py @@ -610,28 +610,6 @@ def trimmed_after_convolution_from( store_native=self.store_native, ) - def hdu_for_output_from( - self, ext_name: Optional[str] = None, return_as_primary: bool = False - ) -> Union[fits.PrimaryHDU, fits.ImageHDU]: - """ - The array as an HDU object, which can be output to a .fits file. - - The header of the HDU is used to store the `pixel_scale` of the array, which is used by the `Array2D.from_hdu`. - - This method is used in other projects (E.g. PyAutoGalaxy, PyAutoLens) to conveniently output the array to .fits - files. - - Returns - ------- - The HDU containing the data and its header which can then be written to .fits. - """ - return array_2d_util.hdu_for_output_from( - array_2d=np.array(self.native), - header_dict=self.pixel_scale_header, - ext_name=ext_name, - return_as_primary=return_as_primary, - ) - class Array2D(AbstractArray2D): @classmethod diff --git a/autoarray/structures/visibilities.py b/autoarray/structures/visibilities.py index 55ad651b6..c49559313 100644 --- a/autoarray/structures/visibilities.py +++ b/autoarray/structures/visibilities.py @@ -1,7 +1,5 @@ from abc import ABC -from astropy.io import fits - import logging import numpy as np from pathlib import Path @@ -13,8 +11,6 @@ from autoarray.structures.abstract_structure import Structure from autoarray.structures.grids.irregular_2d import Grid2DIrregular -from autoarray.structures.arrays import array_2d_util - logging.basicConfig() logger = logging.getLogger(__name__) @@ -103,22 +99,6 @@ def amplitudes(self) -> np.ndarray: def phases(self) -> np.ndarray: return np.arctan2(self.imag, self.real) - @property - def hdu_for_output(self) -> fits.PrimaryHDU: - """ - The visibilities as an HDU object, which can be output to a .fits file. - - This method is used in other projects (E.g. PyAutoGalaxy, PyAutoLens) to conveniently output the array to .fits - files. - - Returns - ------- - The HDU containing the data which can then be written to .fits. - """ - return array_2d_util.hdu_for_output_from( - array_2d=self.in_array, - ) - def output_to_fits(self, file_path: Union[Path, str], overwrite: bool = False): """ Output the visibilities to a .fits file. From bb30c04bfec2294696a961ffb24f734d1692fcd4 Mon Sep 17 00:00:00 2001 From: James Nightingale Date: Mon, 24 Mar 2025 18:50:09 +0000 Subject: [PATCH 11/13] black --- autoarray/abstract_ndarray.py | 2 +- autoarray/mask/abstract_mask.py | 6 +++--- autoarray/mask/mask_1d.py | 5 +++-- autoarray/mask/mask_2d.py | 4 +++- autoarray/structures/abstract_structure.py | 4 ++-- autoarray/structures/arrays/array_1d_util.py | 2 -- autoarray/structures/arrays/array_2d_util.py | 3 --- autoarray/structures/arrays/kernel_2d.py | 1 - autoarray/structures/arrays/uniform_1d.py | 5 +---- autoarray/structures/arrays/uniform_2d.py | 5 +---- autoarray/structures/visibilities.py | 8 ++------ test_autoarray/mask/test_mask_2d.py | 7 ++++--- .../arrays/files/array/output_test/array.fits | Bin 5760 -> 5760 bytes .../structures/arrays/test_uniform_1d.py | 1 + .../structures/arrays/test_uniform_2d.py | 1 + .../plot/test_structure_plotters.py | 4 +--- 16 files changed, 23 insertions(+), 35 deletions(-) diff --git a/autoarray/abstract_ndarray.py b/autoarray/abstract_ndarray.py index 8da2cad61..a5822ef1c 100644 --- a/autoarray/abstract_ndarray.py +++ b/autoarray/abstract_ndarray.py @@ -285,7 +285,7 @@ def output_to_fits(self, file_path: str, overwrite: bool = False): values=self.native.array, file_path=file_path, overwrite=overwrite, - header_dict=self.mask.pixel_scale_header + header_dict=self.mask.header_dict, ) @property diff --git a/autoarray/mask/abstract_mask.py b/autoarray/mask/abstract_mask.py index 717ce9e94..c077d69e7 100644 --- a/autoarray/mask/abstract_mask.py +++ b/autoarray/mask/abstract_mask.py @@ -77,7 +77,7 @@ def pixel_scale(self) -> float: return self.pixel_scales[0] @property - def pixel_scale_header(self) -> Dict: + def header_dict(self) -> Dict: """ Returns the pixel scale of the mask as a header dictionary, which can be written to a .fits file. @@ -122,8 +122,8 @@ def output_to_fits(self, file_path, overwrite=False): values=self.astype("float"), file_path=file_path, overwrite=overwrite, - header_dict=self.pixel_scale_header, - ext_name="mask" + header_dict=self.header_dict, + ext_name="mask", ) @property diff --git a/autoarray/mask/mask_1d.py b/autoarray/mask/mask_1d.py index ab64b8657..d922fcb07 100644 --- a/autoarray/mask/mask_1d.py +++ b/autoarray/mask/mask_1d.py @@ -161,7 +161,7 @@ def shape_slim(self) -> Tuple[int]: return self.shape @property - def pixel_scale_header(self) -> Dict: + def header_dict(self) -> Dict: """ Returns the pixel scales of the mask as a header dictionary, which can be written to a .fits file. @@ -174,4 +174,5 @@ def pixel_scale_header(self) -> Dict: """ return { "PIXSCA": self.pixel_scales[0], - } \ No newline at end of file + "ORIGIN": self.origin[0], + } diff --git a/autoarray/mask/mask_2d.py b/autoarray/mask/mask_2d.py index ec7585702..2cc1da8c4 100644 --- a/autoarray/mask/mask_2d.py +++ b/autoarray/mask/mask_2d.py @@ -705,7 +705,7 @@ def unmasked_blurred_array_from(self, padded_array, psf, image_shape) -> Array2D ) @property - def pixel_scale_header(self) -> Dict: + def header_dict(self) -> Dict: """ Returns the pixel scales of the mask as a header dictionary, which can be written to a .fits file. @@ -719,6 +719,8 @@ def pixel_scale_header(self) -> Dict: return { "PIXSCAY": self.pixel_scales[0], "PIXSCAX": self.pixel_scales[1], + "ORIGINY": self.origin[0], + "ORIGINX": self.origin[1], } @property diff --git a/autoarray/structures/abstract_structure.py b/autoarray/structures/abstract_structure.py index 193680980..d70a6ceea 100644 --- a/autoarray/structures/abstract_structure.py +++ b/autoarray/structures/abstract_structure.py @@ -60,8 +60,8 @@ def pixel_scale(self) -> float: return self.mask.pixel_scale @property - def pixel_scale_header(self) -> Dict: - return self.mask.pixel_scale_header + def header_dict(self) -> Dict: + return self.mask.header_dict @property def pixel_area(self): diff --git a/autoarray/structures/arrays/array_1d_util.py b/autoarray/structures/arrays/array_1d_util.py index 7efe7d8b1..5e6565326 100644 --- a/autoarray/structures/arrays/array_1d_util.py +++ b/autoarray/structures/arrays/array_1d_util.py @@ -185,5 +185,3 @@ def array_1d_via_indexes_1d_from( ] return array_1d_native - - diff --git a/autoarray/structures/arrays/array_2d_util.py b/autoarray/structures/arrays/array_2d_util.py index 4eb196bf8..7fab50b49 100644 --- a/autoarray/structures/arrays/array_2d_util.py +++ b/autoarray/structures/arrays/array_2d_util.py @@ -704,6 +704,3 @@ def array_2d_native_complex_via_indexes_from( ] = array_2d_slim[slim_index] return array_2d - - - diff --git a/autoarray/structures/arrays/kernel_2d.py b/autoarray/structures/arrays/kernel_2d.py index 1e7650b0a..f21cf5a80 100644 --- a/autoarray/structures/arrays/kernel_2d.py +++ b/autoarray/structures/arrays/kernel_2d.py @@ -1,4 +1,3 @@ -from astropy.io import fits from astropy import units import numpy as np import scipy.signal diff --git a/autoarray/structures/arrays/uniform_1d.py b/autoarray/structures/arrays/uniform_1d.py index de460885b..4dbf1692f 100644 --- a/autoarray/structures/arrays/uniform_1d.py +++ b/autoarray/structures/arrays/uniform_1d.py @@ -1,4 +1,3 @@ -from astropy.io import fits import numpy as np from pathlib import Path from typing import Optional, Union, Tuple, List @@ -217,9 +216,7 @@ def from_fits( origin The (x,) scaled units origin of the coordinate system. """ - array_1d = ndarray_via_fits_from( - file_path=file_path, hdu=hdu - ) + array_1d = ndarray_via_fits_from(file_path=file_path, hdu=hdu) header_sci_obj = header_obj_from(file_path=file_path, hdu=0) header_hdu_obj = header_obj_from(file_path=file_path, hdu=hdu) diff --git a/autoarray/structures/arrays/uniform_2d.py b/autoarray/structures/arrays/uniform_2d.py index fd7041c9b..4406db105 100644 --- a/autoarray/structures/arrays/uniform_2d.py +++ b/autoarray/structures/arrays/uniform_2d.py @@ -1,4 +1,3 @@ -from astropy.io import fits import logging import numpy as np from pathlib import Path @@ -891,9 +890,7 @@ def from_fits( pixel_scales=1.0, ) """ - array_2d = ndarray_via_fits_from( - file_path=file_path, hdu=hdu - ) + array_2d = ndarray_via_fits_from(file_path=file_path, hdu=hdu) header_sci_obj = header_obj_from(file_path=file_path, hdu=0) header_hdu_obj = header_obj_from(file_path=file_path, hdu=hdu) diff --git a/autoarray/structures/visibilities.py b/autoarray/structures/visibilities.py index c49559313..2b4113c7c 100644 --- a/autoarray/structures/visibilities.py +++ b/autoarray/structures/visibilities.py @@ -114,9 +114,7 @@ def output_to_fits(self, file_path: Union[Path, str], overwrite: bool = False): overwrite If a file already exists at the path, if overwrite=True it is overwritten else an error is raised. """ - output_to_fits( - values=self.in_array, file_path=file_path, overwrite=overwrite - ) + output_to_fits(values=self.in_array, file_path=file_path, overwrite=overwrite) @property def scaled_maxima(self) -> Tuple[float, float]: @@ -204,9 +202,7 @@ def from_fits(cls, file_path: Union[Path, str], hdu: int) -> "Visibilities": hdu The Header-Data Unit of the .fits file the visibilitiy data is loaded from. """ - visibilities_1d = ndarray_via_fits_from( - file_path=file_path, hdu=hdu - ) + visibilities_1d = ndarray_via_fits_from(file_path=file_path, hdu=hdu) return cls(visibilities=visibilities_1d) diff --git a/test_autoarray/mask/test_mask_2d.py b/test_autoarray/mask/test_mask_2d.py index 3e8ad8b87..fda07c66d 100644 --- a/test_autoarray/mask/test_mask_2d.py +++ b/test_autoarray/mask/test_mask_2d.py @@ -359,11 +359,12 @@ def test__from_fits__output_to_fits(): assert mask.pixel_scales == (1.0, 1.0) assert mask.origin == (2.0, 2.0) - header = aa.header_obj_from( - file_path=path.join(test_data_path, "mask.fits"), hdu=0 - ) + header = aa.header_obj_from(file_path=path.join(test_data_path, "mask.fits"), hdu=0) assert header["PIXSCAY"] == 1.0 + assert header["PIXSCAX"] == 1.0 + assert header["ORIGINY"] == 0.0 + assert header["ORIGINX"] == 0.0 def test__from_fits__with_resized_mask_shape(): diff --git a/test_autoarray/structures/arrays/files/array/output_test/array.fits b/test_autoarray/structures/arrays/files/array/output_test/array.fits index 712bb607d50595518eed059269b771a085467a17..0cff0a8f7db90d3ded28c644cd66e3791627feeb 100644 GIT binary patch delta 79 vcmZqBZP49dz$D`z Date: Wed, 2 Apr 2025 15:20:32 +0100 Subject: [PATCH 12/13] support enums --- autoarray/mask/abstract_mask.py | 3 +-- autoarray/mask/mask_1d.py | 9 +++++++-- autoarray/mask/mask_2d.py | 16 ++++++++++++---- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/autoarray/mask/abstract_mask.py b/autoarray/mask/abstract_mask.py index c077d69e7..3fa45cf0e 100644 --- a/autoarray/mask/abstract_mask.py +++ b/autoarray/mask/abstract_mask.py @@ -3,8 +3,7 @@ from abc import ABC import logging import numpy as np -from pathlib import Path -from typing import Dict, Union +from typing import Dict from autoconf.fitsable import output_to_fits diff --git a/autoarray/mask/mask_1d.py b/autoarray/mask/mask_1d.py index d922fcb07..3004d6472 100644 --- a/autoarray/mask/mask_1d.py +++ b/autoarray/mask/mask_1d.py @@ -1,5 +1,6 @@ from __future__ import annotations +from enum import Enum import logging import numpy as np from pathlib import Path @@ -172,7 +173,11 @@ def header_dict(self) -> Dict: ------- A dictionary containing the pixel scale of the mask, which can be output to a .fits file. """ + class GridKeys(Enum): + PIXSCA = "PIXSCA" + ORIGIN = "ORIGIN" + return { - "PIXSCA": self.pixel_scales[0], - "ORIGIN": self.origin[0], + GridKeys.PIXSCA: self.pixel_scales[0], + GridKeys.ORIGIN: self.origin[0], } diff --git a/autoarray/mask/mask_2d.py b/autoarray/mask/mask_2d.py index 2cc1da8c4..b01dbf969 100644 --- a/autoarray/mask/mask_2d.py +++ b/autoarray/mask/mask_2d.py @@ -1,5 +1,6 @@ from __future__ import annotations import logging +from enum import Enum import numpy as np from pathlib import Path from typing import TYPE_CHECKING, Dict, List, Tuple, Union @@ -716,11 +717,18 @@ def header_dict(self) -> Dict: ------- A dictionary containing the pixel scale of the mask, which can be output to a .fits file. """ + + class GridKeys(Enum): + PIXSCAY = "PIXSCAY" + PIXSCAX = "PIXSCAX" + ORIGINY = "ORIGINY" + ORIGINX = "ORIGINX" + return { - "PIXSCAY": self.pixel_scales[0], - "PIXSCAX": self.pixel_scales[1], - "ORIGINY": self.origin[0], - "ORIGINX": self.origin[1], + GridKeys.PIXSCAY: self.pixel_scales[0], + GridKeys.PIXSCAX: self.pixel_scales[1], + GridKeys.ORIGINY: self.origin[0], + GridKeys.ORIGINX: self.origin[1], } @property From 0b5f95b319fd4395d72eadc314723f81280ba518 Mon Sep 17 00:00:00 2001 From: James Nightingale Date: Wed, 2 Apr 2025 15:45:42 +0100 Subject: [PATCH 13/13] fin --- autoarray/mask/mask_1d.py | 11 ++++++----- autoarray/mask/mask_2d.py | 21 +++++++++++---------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/autoarray/mask/mask_1d.py b/autoarray/mask/mask_1d.py index 3004d6472..9330e62ee 100644 --- a/autoarray/mask/mask_1d.py +++ b/autoarray/mask/mask_1d.py @@ -21,6 +21,10 @@ logger = logging.getLogger(__name__) +class Mask1DKeys(Enum): + PIXSCA = "PIXSCA" + ORIGIN = "ORIGIN" + class Mask1D(Mask): def __init__( self, @@ -173,11 +177,8 @@ def header_dict(self) -> Dict: ------- A dictionary containing the pixel scale of the mask, which can be output to a .fits file. """ - class GridKeys(Enum): - PIXSCA = "PIXSCA" - ORIGIN = "ORIGIN" return { - GridKeys.PIXSCA: self.pixel_scales[0], - GridKeys.ORIGIN: self.origin[0], + Mask1DKeys.PIXSCA: self.pixel_scales[0], + Mask1DKeys.ORIGIN: self.origin[0], } diff --git a/autoarray/mask/mask_2d.py b/autoarray/mask/mask_2d.py index b01dbf969..b47b8eef7 100644 --- a/autoarray/mask/mask_2d.py +++ b/autoarray/mask/mask_2d.py @@ -31,6 +31,13 @@ logger = logging.getLogger(__name__) +class Mask2DKeys(Enum): + PIXSCAY = "PIXSCAY" + PIXSCAX = "PIXSCAX" + ORIGINY = "ORIGINY" + ORIGINX = "ORIGINX" + + class Mask2D(Mask): # noinspection PyUnusedLocal def __init__( @@ -718,17 +725,11 @@ def header_dict(self) -> Dict: A dictionary containing the pixel scale of the mask, which can be output to a .fits file. """ - class GridKeys(Enum): - PIXSCAY = "PIXSCAY" - PIXSCAX = "PIXSCAX" - ORIGINY = "ORIGINY" - ORIGINX = "ORIGINX" - return { - GridKeys.PIXSCAY: self.pixel_scales[0], - GridKeys.PIXSCAX: self.pixel_scales[1], - GridKeys.ORIGINY: self.origin[0], - GridKeys.ORIGINX: self.origin[1], + Mask2DKeys.PIXSCAY: self.pixel_scales[0], + Mask2DKeys.PIXSCAX: self.pixel_scales[1], + Mask2DKeys.ORIGINY: self.origin[0], + Mask2DKeys.ORIGINX: self.origin[1], } @property