From 6ba956633a358ca03ab8f6e33a441529150cf693 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Mon, 21 Apr 2025 09:08:41 +0100 Subject: [PATCH 01/13] added zoom_2d methods to standalone module --- autoarray/mask/mask_2d.py | 89 -------------------- autoarray/operators/zoom_2d.py | 103 +++++++++++++++++++++++ test_autoarray/operators/test_zoom_2d.py | 0 3 files changed, 103 insertions(+), 89 deletions(-) create mode 100644 autoarray/operators/zoom_2d.py create mode 100644 test_autoarray/operators/test_zoom_2d.py diff --git a/autoarray/mask/mask_2d.py b/autoarray/mask/mask_2d.py index b47b8eef7..f08567468 100644 --- a/autoarray/mask/mask_2d.py +++ b/autoarray/mask/mask_2d.py @@ -872,95 +872,6 @@ def resized_from(self, new_shape, pad_value: int = 0.0) -> Mask2D: origin=self.origin, ) - @property - def zoom_centre(self) -> Tuple[float, float]: - from autoarray.structures.grids.uniform_2d import Grid2D - - grid = grid_2d_util.grid_2d_slim_via_mask_from( - mask_2d=np.array(self), - pixel_scales=self.pixel_scales, - origin=self.origin, - ) - - grid = Grid2D(values=grid, mask=self) - - extraction_grid_1d = self.geometry.grid_pixels_2d_from(grid_scaled_2d=grid) - y_pixels_max = np.max(extraction_grid_1d[:, 0]) - y_pixels_min = np.min(extraction_grid_1d[:, 0]) - x_pixels_max = np.max(extraction_grid_1d[:, 1]) - x_pixels_min = np.min(extraction_grid_1d[:, 1]) - - return ( - ((y_pixels_max + y_pixels_min - 1.0) / 2.0), - ((x_pixels_max + x_pixels_min - 1.0) / 2.0), - ) - - @property - def zoom_offset_pixels(self) -> Tuple[float, float]: - if self.pixel_scales is None: - return self.geometry.central_pixel_coordinates - - return ( - self.zoom_centre[0] - self.geometry.central_pixel_coordinates[0], - self.zoom_centre[1] - self.geometry.central_pixel_coordinates[1], - ) - - @property - def zoom_offset_scaled(self) -> Tuple[float, float]: - return ( - -self.pixel_scales[0] * self.zoom_offset_pixels[0], - self.pixel_scales[1] * self.zoom_offset_pixels[1], - ) - - @property - def zoom_region(self) -> List[int]: - """ - The zoomed rectangular region corresponding to the square encompassing all unmasked values. This zoomed - extraction region is a squuare, even if the mask is rectangular. - - This is used to zoom in on the region of an image that is used in an analysis for visualization. - """ - - where = np.array(np.where(np.invert(self.astype("bool")))) - y0, x0 = np.amin(where, axis=1) - y1, x1 = np.amax(where, axis=1) - - # Have to convert mask to bool for invert function to work. - - ylength = y1 - y0 - xlength = x1 - x0 - - if ylength > xlength: - length_difference = ylength - xlength - x1 += int(length_difference / 2.0) - x0 -= int(length_difference / 2.0) - elif xlength > ylength: - length_difference = xlength - ylength - y1 += int(length_difference / 2.0) - y0 -= int(length_difference / 2.0) - - return [y0, y1 + 1, x0, x1 + 1] - - @property - def zoom_shape_native(self) -> Tuple[int, int]: - region = self.zoom_region - return (region[1] - region[0], region[3] - region[2]) - - @property - def zoom_mask_unmasked(self) -> "Mask2D": - """ - The scaled-grid of (y,x) coordinates of every pixel. - - This is defined from the top-left corner, such that the first pixel at location [0, 0] will have a negative x - value y value in scaled units. - """ - - return Mask2D.all_false( - shape_native=self.zoom_shape_native, - pixel_scales=self.pixel_scales, - origin=self.zoom_offset_scaled, - ) - @property def is_circular(self) -> bool: """ diff --git a/autoarray/operators/zoom_2d.py b/autoarray/operators/zoom_2d.py new file mode 100644 index 000000000..0205e31d1 --- /dev/null +++ b/autoarray/operators/zoom_2d.py @@ -0,0 +1,103 @@ +from __future__ import annotations +import numpy as np +from typing import List, Tuple, Union + +from autoarray.mask.mask_2d import Mask2D + +from autoarray.structures.grids import grid_2d_util + + +class Zoom2D: + + def __init__(self, mask: Union[np.ndarray, List]): + + self.mask = mask + + @property + def zoom_centre(self) -> Tuple[float, float]: + from autoarray.structures.grids.uniform_2d import Grid2D + + grid = grid_2d_util.grid_2d_slim_via_mask_from( + mask_2d=np.array(self), + pixel_scales=self.pixel_scales, + origin=self.origin, + ) + + grid = Grid2D(values=grid, mask=self) + + extraction_grid_1d = self.geometry.grid_pixels_2d_from(grid_scaled_2d=grid) + y_pixels_max = np.max(extraction_grid_1d[:, 0]) + y_pixels_min = np.min(extraction_grid_1d[:, 0]) + x_pixels_max = np.max(extraction_grid_1d[:, 1]) + x_pixels_min = np.min(extraction_grid_1d[:, 1]) + + return ( + ((y_pixels_max + y_pixels_min - 1.0) / 2.0), + ((x_pixels_max + x_pixels_min - 1.0) / 2.0), + ) + + @property + def zoom_offset_pixels(self) -> Tuple[float, float]: + if self.pixel_scales is None: + return self.geometry.central_pixel_coordinates + + return ( + self.zoom_centre[0] - self.geometry.central_pixel_coordinates[0], + self.zoom_centre[1] - self.geometry.central_pixel_coordinates[1], + ) + + @property + def zoom_offset_scaled(self) -> Tuple[float, float]: + return ( + -self.pixel_scales[0] * self.zoom_offset_pixels[0], + self.pixel_scales[1] * self.zoom_offset_pixels[1], + ) + + @property + def zoom_region(self) -> List[int]: + """ + The zoomed rectangular region corresponding to the square encompassing all unmasked values. This zoomed + extraction region is a squuare, even if the mask is rectangular. + + This is used to zoom in on the region of an image that is used in an analysis for visualization. + """ + + where = np.array(np.where(np.invert(self.astype("bool")))) + y0, x0 = np.amin(where, axis=1) + y1, x1 = np.amax(where, axis=1) + + # Have to convert mask to bool for invert function to work. + + ylength = y1 - y0 + xlength = x1 - x0 + + if ylength > xlength: + length_difference = ylength - xlength + x1 += int(length_difference / 2.0) + x0 -= int(length_difference / 2.0) + elif xlength > ylength: + length_difference = xlength - ylength + y1 += int(length_difference / 2.0) + y0 -= int(length_difference / 2.0) + + return [y0, y1 + 1, x0, x1 + 1] + + @property + def zoom_shape_native(self) -> Tuple[int, int]: + region = self.zoom_region + return (region[1] - region[0], region[3] - region[2]) + + @property + def zoom_mask_unmasked(self) -> "Mask2D": + """ + The scaled-grid of (y,x) coordinates of every pixel. + + This is defined from the top-left corner, such that the first pixel at location [0, 0] will have a negative x + value y value in scaled units. + """ + + return Mask2D.all_false( + shape_native=self.zoom_shape_native, + pixel_scales=self.pixel_scales, + origin=self.zoom_offset_scaled, + ) \ No newline at end of file diff --git a/test_autoarray/operators/test_zoom_2d.py b/test_autoarray/operators/test_zoom_2d.py new file mode 100644 index 000000000..e69de29bb From 54b94ef8696aee3c01cc10017b71e3d4e9ca7801 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Mon, 21 Apr 2025 09:21:58 +0100 Subject: [PATCH 02/13] all Zoom unit tests pass --- autoarray/__init__.py | 1 + .../{operators => mask/derive}/zoom_2d.py | 48 ++-- autoarray/mask/mask_2d.py | 5 + test_autoarray/mask/derive/test_zoom_2d.py | 149 +++++++++++ test_autoarray/mask/test_mask_2d.py | 252 ------------------ test_autoarray/operators/test_zoom_2d.py | 0 6 files changed, 179 insertions(+), 276 deletions(-) rename autoarray/{operators => mask/derive}/zoom_2d.py (62%) create mode 100644 test_autoarray/mask/derive/test_zoom_2d.py delete mode 100644 test_autoarray/operators/test_zoom_2d.py diff --git a/autoarray/__init__.py b/autoarray/__init__.py index 18518971d..b48a9c2ae 100644 --- a/autoarray/__init__.py +++ b/autoarray/__init__.py @@ -53,6 +53,7 @@ from .mask.derive.mask_2d import DeriveMask2D from .mask.derive.grid_1d import DeriveGrid1D from .mask.derive.grid_2d import DeriveGrid2D +from .mask.derive.zoom_2d import Zoom2D from .mask.mask_1d import Mask1D from .mask.mask_2d import Mask2D from .operators.convolver import Convolver diff --git a/autoarray/operators/zoom_2d.py b/autoarray/mask/derive/zoom_2d.py similarity index 62% rename from autoarray/operators/zoom_2d.py rename to autoarray/mask/derive/zoom_2d.py index 0205e31d1..bc987d555 100644 --- a/autoarray/operators/zoom_2d.py +++ b/autoarray/mask/derive/zoom_2d.py @@ -2,8 +2,6 @@ import numpy as np from typing import List, Tuple, Union -from autoarray.mask.mask_2d import Mask2D - from autoarray.structures.grids import grid_2d_util @@ -14,18 +12,18 @@ def __init__(self, mask: Union[np.ndarray, List]): self.mask = mask @property - def zoom_centre(self) -> Tuple[float, float]: + def centre(self) -> Tuple[float, float]: from autoarray.structures.grids.uniform_2d import Grid2D grid = grid_2d_util.grid_2d_slim_via_mask_from( - mask_2d=np.array(self), - pixel_scales=self.pixel_scales, - origin=self.origin, + mask_2d=np.array(self.mask), + pixel_scales=self.mask.pixel_scales, + origin=self.mask.origin, ) - grid = Grid2D(values=grid, mask=self) + grid = Grid2D(values=grid, mask=self.mask) - extraction_grid_1d = self.geometry.grid_pixels_2d_from(grid_scaled_2d=grid) + extraction_grid_1d = self.mask.geometry.grid_pixels_2d_from(grid_scaled_2d=grid) y_pixels_max = np.max(extraction_grid_1d[:, 0]) y_pixels_min = np.min(extraction_grid_1d[:, 0]) x_pixels_max = np.max(extraction_grid_1d[:, 1]) @@ -37,24 +35,24 @@ def zoom_centre(self) -> Tuple[float, float]: ) @property - def zoom_offset_pixels(self) -> Tuple[float, float]: - if self.pixel_scales is None: - return self.geometry.central_pixel_coordinates + def offset_pixels(self) -> Tuple[float, float]: + if self.mask.pixel_scales is None: + return self.mask.geometry.central_pixel_coordinates return ( - self.zoom_centre[0] - self.geometry.central_pixel_coordinates[0], - self.zoom_centre[1] - self.geometry.central_pixel_coordinates[1], + self.centre[0] - self.mask.geometry.central_pixel_coordinates[0], + self.centre[1] - self.mask.geometry.central_pixel_coordinates[1], ) @property - def zoom_offset_scaled(self) -> Tuple[float, float]: + def offset_scaled(self) -> Tuple[float, float]: return ( - -self.pixel_scales[0] * self.zoom_offset_pixels[0], - self.pixel_scales[1] * self.zoom_offset_pixels[1], + -self.mask.pixel_scales[0] * self.offset_pixels[0], + self.mask.pixel_scales[1] * self.offset_pixels[1], ) @property - def zoom_region(self) -> List[int]: + def region(self) -> List[int]: """ The zoomed rectangular region corresponding to the square encompassing all unmasked values. This zoomed extraction region is a squuare, even if the mask is rectangular. @@ -62,7 +60,7 @@ def zoom_region(self) -> List[int]: This is used to zoom in on the region of an image that is used in an analysis for visualization. """ - where = np.array(np.where(np.invert(self.astype("bool")))) + where = np.array(np.where(np.invert(self.mask.astype("bool")))) y0, x0 = np.amin(where, axis=1) y1, x1 = np.amax(where, axis=1) @@ -83,12 +81,12 @@ def zoom_region(self) -> List[int]: return [y0, y1 + 1, x0, x1 + 1] @property - def zoom_shape_native(self) -> Tuple[int, int]: - region = self.zoom_region + def shape_native(self) -> Tuple[int, int]: + region = self.region return (region[1] - region[0], region[3] - region[2]) @property - def zoom_mask_unmasked(self) -> "Mask2D": + def mask_unmasked(self) -> "Mask2D": """ The scaled-grid of (y,x) coordinates of every pixel. @@ -96,8 +94,10 @@ def zoom_mask_unmasked(self) -> "Mask2D": value y value in scaled units. """ + from autoarray.mask.mask_2d import Mask2D + return Mask2D.all_false( - shape_native=self.zoom_shape_native, - pixel_scales=self.pixel_scales, - origin=self.zoom_offset_scaled, + shape_native=self.shape_native, + pixel_scales=self.mask.pixel_scales, + origin=self.offset_scaled, ) \ No newline at end of file diff --git a/autoarray/mask/mask_2d.py b/autoarray/mask/mask_2d.py index f08567468..6905d9b94 100644 --- a/autoarray/mask/mask_2d.py +++ b/autoarray/mask/mask_2d.py @@ -5,6 +5,7 @@ from pathlib import Path from typing import TYPE_CHECKING, Dict, List, Tuple, Union +from autoarray.mask.derive.zoom_2d import Zoom2D from autoarray.structures.abstract_structure import Structure if TYPE_CHECKING: @@ -253,6 +254,10 @@ def derive_mask(self) -> DeriveMask2D: def derive_grid(self) -> DeriveGrid2D: return DeriveGrid2D(mask=self) + @property + def zoom(self) -> Zoom2D: + return Zoom2D(mask=self) + @classmethod def all_false( cls, diff --git a/test_autoarray/mask/derive/test_zoom_2d.py b/test_autoarray/mask/derive/test_zoom_2d.py new file mode 100644 index 000000000..35168f74e --- /dev/null +++ b/test_autoarray/mask/derive/test_zoom_2d.py @@ -0,0 +1,149 @@ +import numpy as np + +import autoarray as aa + + +def test__quantities(): + + mask = aa.Mask2D.all_false(shape_native=(4, 6), pixel_scales=(1.0, 1.0)) + zoom = aa.Zoom2D(mask=mask) + + assert zoom.centre == (1.5, 2.5) + assert zoom.offset_pixels == (0, 0) + assert zoom.shape_native == (6, 6) + + mask = aa.Mask2D.all_false(shape_native=(6, 4), pixel_scales=(1.0, 1.0)) + zoom = aa.Zoom2D(mask=mask) + + assert zoom.centre == (2.5, 1.5) + assert zoom.offset_pixels == (0, 0) + assert zoom.shape_native == (6, 6) + + mask = aa.Mask2D( + mask=np.array([[True, True, True], [True, True, False], [True, True, True]]), + pixel_scales=(1.0, 1.0), + ) + zoom = aa.Zoom2D(mask=mask) + + assert zoom.centre == (1, 2) + assert zoom.offset_pixels == (0, 1) + assert zoom.shape_native == (1, 1) + + mask = aa.Mask2D( + mask=np.array([[True, True, True], [True, True, True], [True, False, True]]), + pixel_scales=(1.0, 1.0), + ) + zoom = aa.Zoom2D(mask=mask) + + assert zoom.centre == (2, 1) + assert zoom.offset_pixels == (1, 0) + assert zoom.shape_native == (1, 1) + + mask = aa.Mask2D( + mask=np.array([[False, True, False], [True, True, True], [True, True, True]]), + pixel_scales=(1.0, 1.0), + ) + zoom = aa.Zoom2D(mask=mask) + + assert zoom.centre == (0, 1) + assert zoom.offset_pixels == (-1, 0) + assert zoom.shape_native == (3, 3) + + mask = aa.Mask2D( + mask=np.array([[False, False, True], [True, True, True], [True, True, True]]), + pixel_scales=(1.0, 1.0), + ) + zoom = aa.Zoom2D(mask=mask) + + assert zoom.centre == (0, 0.5) + assert zoom.offset_pixels == (-1, -0.5) + assert zoom.shape_native == (1, 2) + + mask = aa.Mask2D( + mask=np.array( + [ + [True, True, True, True, True, True, True], + [True, True, True, True, True, True, True], + [True, True, True, True, True, True, False], + ] + ), + pixel_scales=(1.0, 1.0), + ) + zoom = aa.Zoom2D(mask=mask) + + assert zoom.centre == (2, 6) + assert zoom.offset_pixels == (1, 3) + + mask = aa.Mask2D( + mask=np.array( + [ + [True, True, True], + [True, True, True], + [True, True, True], + [True, True, True], + [True, True, False], + ] + ), + pixel_scales=(1.0, 1.0), + ) + zoom = aa.Zoom2D(mask=mask) + + assert zoom.centre == (4, 2) + assert zoom.offset_pixels == (2, 1) + + mask = aa.Mask2D( + mask=np.array( + [ + [True, True, True], + [True, True, True], + [True, True, True], + [True, True, True], + [True, True, True], + [True, True, True], + [True, True, False], + ] + ), + pixel_scales=(1.0, 1.0), + ) + zoom = aa.Zoom2D(mask=mask) + + assert zoom.centre == (6, 2) + assert zoom.offset_pixels == (3, 1) + + +def test__mask_unmasked(): + mask = aa.Mask2D( + mask=np.array( + [ + [False, True, True, True], + [True, False, True, True], + [True, True, True, True], + ] + ), + pixel_scales=(1.0, 1.0), + ) + zoom = aa.Zoom2D(mask=mask) + + mask_unmasked = zoom.mask_unmasked + + assert (mask_unmasked == np.array([[False, False], [False, False]])).all() + assert mask_unmasked.origin == (0.5, -1.0) + + mask = aa.Mask2D( + mask=np.array( + [ + [False, True, True, True], + [True, False, True, True], + [True, False, True, True], + ] + ), + pixel_scales=(1.0, 2.0), + ) + zoom = aa.Zoom2D(mask=mask) + + mask_unmasked = zoom.mask_unmasked + + assert ( + mask_unmasked == np.array([[False, False], [False, False], [False, False]]) + ).all() + assert mask_unmasked.origin == (0.0, -2.0) \ No newline at end of file diff --git a/test_autoarray/mask/test_mask_2d.py b/test_autoarray/mask/test_mask_2d.py index fda07c66d..9d71c2929 100644 --- a/test_autoarray/mask/test_mask_2d.py +++ b/test_autoarray/mask/test_mask_2d.py @@ -1,4 +1,3 @@ -from astropy.io import fits import os from os import path import numpy as np @@ -527,257 +526,6 @@ def test__resized_from(): assert (mask_resized == mask_resized_manual).all() - -def test__zoom_quantities(): - mask = aa.Mask2D.all_false(shape_native=(3, 5), pixel_scales=(1.0, 1.0)) - assert mask.zoom_centre == (1.0, 2.0) - assert mask.zoom_offset_pixels == (0, 0) - assert mask.zoom_shape_native == (5, 5) - - mask = aa.Mask2D.all_false(shape_native=(5, 3), pixel_scales=(1.0, 1.0)) - assert mask.zoom_centre == (2.0, 1.0) - assert mask.zoom_offset_pixels == (0, 0) - assert mask.zoom_shape_native == (5, 5) - - mask = aa.Mask2D.all_false(shape_native=(4, 6), pixel_scales=(1.0, 1.0)) - assert mask.zoom_centre == (1.5, 2.5) - assert mask.zoom_offset_pixels == (0, 0) - assert mask.zoom_shape_native == (6, 6) - - mask = aa.Mask2D.all_false(shape_native=(6, 4), pixel_scales=(1.0, 1.0)) - assert mask.zoom_centre == (2.5, 1.5) - assert mask.zoom_offset_pixels == (0, 0) - assert mask.zoom_shape_native == (6, 6) - - -def test__mask_is_single_false__extraction_centre_is_central_pixel(): - mask = aa.Mask2D( - mask=np.array([[False, True, True], [True, True, True], [True, True, True]]), - pixel_scales=(1.0, 1.0), - ) - assert mask.zoom_centre == (0, 0) - assert mask.zoom_offset_pixels == (-1, -1) - assert mask.zoom_shape_native == (1, 1) - - mask = aa.Mask2D( - mask=np.array([[True, True, False], [True, True, True], [True, True, True]]), - pixel_scales=(1.0, 1.0), - ) - assert mask.zoom_centre == (0, 2) - assert mask.zoom_offset_pixels == (-1, 1) - assert mask.zoom_shape_native == (1, 1) - - mask = aa.Mask2D( - mask=np.array([[True, True, True], [True, True, True], [False, True, True]]), - pixel_scales=(1.0, 1.0), - ) - assert mask.zoom_centre == (2, 0) - assert mask.zoom_offset_pixels == (1, -1) - assert mask.zoom_shape_native == (1, 1) - - mask = aa.Mask2D( - mask=np.array([[True, True, True], [True, True, True], [True, True, False]]), - pixel_scales=(1.0, 1.0), - ) - assert mask.zoom_centre == (2, 2) - assert mask.zoom_offset_pixels == (1, 1) - assert mask.zoom_shape_native == (1, 1) - - mask = aa.Mask2D( - mask=np.array([[True, False, True], [True, True, True], [True, True, True]]), - pixel_scales=(1.0, 1.0), - ) - assert mask.zoom_centre == (0, 1) - assert mask.zoom_offset_pixels == (-1, 0) - assert mask.zoom_shape_native == (1, 1) - - mask = aa.Mask2D( - mask=np.array([[True, True, True], [False, True, True], [True, True, True]]), - pixel_scales=(1.0, 1.0), - ) - assert mask.zoom_centre == (1, 0) - assert mask.zoom_offset_pixels == (0, -1) - assert mask.zoom_shape_native == (1, 1) - - mask = aa.Mask2D( - mask=np.array([[True, True, True], [True, True, False], [True, True, True]]), - pixel_scales=(1.0, 1.0), - ) - assert mask.zoom_centre == (1, 2) - assert mask.zoom_offset_pixels == (0, 1) - assert mask.zoom_shape_native == (1, 1) - - mask = aa.Mask2D( - mask=np.array([[True, True, True], [True, True, True], [True, False, True]]), - pixel_scales=(1.0, 1.0), - ) - assert mask.zoom_centre == (2, 1) - assert mask.zoom_offset_pixels == (1, 0) - assert mask.zoom_shape_native == (1, 1) - - -def test__mask_is_x2_false__extraction_centre_is_central_pixel(): - mask = aa.Mask2D( - mask=np.array([[False, True, True], [True, True, True], [True, True, False]]), - pixel_scales=(1.0, 1.0), - ) - assert mask.zoom_centre == (1, 1) - assert mask.zoom_offset_pixels == (0, 0) - assert mask.zoom_shape_native == (3, 3) - - mask = aa.Mask2D( - mask=np.array([[False, True, True], [True, True, True], [False, True, True]]), - pixel_scales=(1.0, 1.0), - ) - assert mask.zoom_centre == (1, 0) - assert mask.zoom_offset_pixels == (0, -1) - assert mask.zoom_shape_native == (3, 3) - - mask = aa.Mask2D( - mask=np.array([[False, True, False], [True, True, True], [True, True, True]]), - pixel_scales=(1.0, 1.0), - ) - assert mask.zoom_centre == (0, 1) - assert mask.zoom_offset_pixels == (-1, 0) - assert mask.zoom_shape_native == (3, 3) - - mask = aa.Mask2D( - mask=np.array([[False, False, True], [True, True, True], [True, True, True]]), - pixel_scales=(1.0, 1.0), - ) - assert mask.zoom_centre == (0, 0.5) - assert mask.zoom_offset_pixels == (-1, -0.5) - assert mask.zoom_shape_native == (1, 2) - - -def test__rectangular_mask(): - mask = aa.Mask2D( - mask=np.array( - [ - [False, True, True, True], - [True, True, True, True], - [True, True, True, True], - ] - ), - pixel_scales=(1.0, 1.0), - ) - - assert mask.zoom_centre == (0, 0) - assert mask.zoom_offset_pixels == (-1.0, -1.5) - - mask = aa.Mask2D( - mask=np.array( - [ - [True, True, True, True], - [True, True, True, True], - [True, True, True, False], - ] - ), - pixel_scales=(1.0, 1.0), - ) - - assert mask.zoom_centre == (2, 3) - assert mask.zoom_offset_pixels == (1.0, 1.5) - - mask = aa.Mask2D( - mask=np.array( - [ - [True, True, True, True, True], - [True, True, True, True, True], - [True, True, True, True, False], - ] - ), - pixel_scales=(1.0, 1.0), - ) - - assert mask.zoom_centre == (2, 4) - assert mask.zoom_offset_pixels == (1, 2) - - mask = aa.Mask2D( - mask=np.array( - [ - [True, True, True, True, True, True, True], - [True, True, True, True, True, True, True], - [True, True, True, True, True, True, False], - ] - ), - pixel_scales=(1.0, 1.0), - ) - - assert mask.zoom_centre == (2, 6) - assert mask.zoom_offset_pixels == (1, 3) - - mask = aa.Mask2D( - mask=np.array( - [ - [True, True, True], - [True, True, True], - [True, True, True], - [True, True, True], - [True, True, False], - ] - ), - pixel_scales=(1.0, 1.0), - ) - - assert mask.zoom_centre == (4, 2) - assert mask.zoom_offset_pixels == (2, 1) - - mask = aa.Mask2D( - mask=np.array( - [ - [True, True, True], - [True, True, True], - [True, True, True], - [True, True, True], - [True, True, True], - [True, True, True], - [True, True, False], - ] - ), - pixel_scales=(1.0, 1.0), - ) - - assert mask.zoom_centre == (6, 2) - assert mask.zoom_offset_pixels == (3, 1) - - -def test__zoom_mask_unmasked(): - mask = aa.Mask2D( - mask=np.array( - [ - [False, True, True, True], - [True, False, True, True], - [True, True, True, True], - ] - ), - pixel_scales=(1.0, 1.0), - ) - - zoom_mask = mask.zoom_mask_unmasked - - assert (zoom_mask == np.array([[False, False], [False, False]])).all() - assert zoom_mask.origin == (0.5, -1.0) - - mask = aa.Mask2D( - mask=np.array( - [ - [False, True, True, True], - [True, False, True, True], - [True, False, True, True], - ] - ), - pixel_scales=(1.0, 2.0), - ) - - zoom_mask = mask.zoom_mask_unmasked - - assert ( - zoom_mask == np.array([[False, False], [False, False], [False, False]]) - ).all() - assert zoom_mask.origin == (0.0, -2.0) - - def test__mask_centre(): mask = np.array( [ diff --git a/test_autoarray/operators/test_zoom_2d.py b/test_autoarray/operators/test_zoom_2d.py deleted file mode 100644 index e69de29bb..000000000 From 50e45068aec885505576423cf2a24c7d5981bb87 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Mon, 21 Apr 2025 09:26:36 +0100 Subject: [PATCH 03/13] refactor plot in zoom --- autoarray/mask/derive/zoom_2d.py | 2 + autoarray/plot/mat_plot/two_d.py | 69 ++++++++++---------------------- 2 files changed, 23 insertions(+), 48 deletions(-) diff --git a/autoarray/mask/derive/zoom_2d.py b/autoarray/mask/derive/zoom_2d.py index bc987d555..cc9d4b8ac 100644 --- a/autoarray/mask/derive/zoom_2d.py +++ b/autoarray/mask/derive/zoom_2d.py @@ -2,6 +2,8 @@ import numpy as np from typing import List, Tuple, Union +from autoconf import conf + from autoarray.structures.grids import grid_2d_util diff --git a/autoarray/plot/mat_plot/two_d.py b/autoarray/plot/mat_plot/two_d.py index 9fcec3657..0200537bb 100644 --- a/autoarray/plot/mat_plot/two_d.py +++ b/autoarray/plot/mat_plot/two_d.py @@ -1,6 +1,6 @@ import matplotlib.pyplot as plt import numpy as np -from typing import Optional, List, Tuple, Union +from typing import Optional, List, Union from autoconf import conf @@ -220,52 +220,6 @@ def __init__( self.is_for_subplot = False - def zoomed_array_and_extent_from(self, array) -> Tuple[np.ndarray, Tuple]: - """ - Returns the array and extent of the array, zoomed around the mask of the array, if the config file is set to - do this. - - Many plots zoom in around the mask of an array, to emphasize the signal of the data and not waste - plotting space on empty pixels. This function computes the zoomed array and extent of this array, given the - array. - - If the mask is all false, the array is returned without zooming by disabling the buffer. - - Parameters - ---------- - array - - Returns - ------- - - """ - - if array.mask.is_all_false: - buffer = 0 - else: - buffer = 1 - - zoom_around_mask = conf.instance["visualize"]["general"]["general"][ - "zoom_around_mask" - ] - - if ( - self.output.format == "fits" - and conf.instance["visualize"]["general"]["general"][ - "disable_zoom_for_fits" - ] - ): - zoom_around_mask = False - - if zoom_around_mask: - extent = array.extent_of_zoomed_array(buffer=buffer) - array = array.zoomed_around_mask(buffer=buffer) - - else: - extent = array.geometry.extent - - return array, extent - def plot_array( self, array: Array2D, @@ -301,7 +255,26 @@ def plot_array( "a pixel scales attribute." ) - array, extent = self.zoomed_array_and_extent_from(array=array) + if conf.instance["visualize"]["general"]["general"][ + "zoom_around_mask" + ]: + + buffer = 0 if array.mask.is_all_false else 1 + + array = array.zoomed_around_mask(buffer=buffer) + extent = array.extent_of_zoomed_array(buffer=buffer) + + else: + + extent = array.geometry.extent + + if ( + self.output.format == "fits" + and conf.instance["visualize"]["general"]["general"][ + "disable_zoom_for_fits" + ] + ): + zoom_around_mask = False ax = None From a0e4d0e6235a88e56232616468b91f3f32bed083 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Mon, 21 Apr 2025 09:28:33 +0100 Subject: [PATCH 04/13] clean up config --- autoarray/config/visualize/general.yaml | 1 - autoarray/mask/derive/zoom_2d.py | 2 -- autoarray/plot/mat_plot/two_d.py | 8 -------- test_autoarray/config/visualize.yaml | 1 - 4 files changed, 12 deletions(-) diff --git a/autoarray/config/visualize/general.yaml b/autoarray/config/visualize/general.yaml index 8bbf29e06..b6cecf50f 100644 --- a/autoarray/config/visualize/general.yaml +++ b/autoarray/config/visualize/general.yaml @@ -4,7 +4,6 @@ general: log10_min_value: 1.0e-4 # If negative values are being plotted on a log10 scale, values below this value are rounded up to it (e.g. to remove negative values). log10_max_value: 1.0e99 # If positive values are being plotted on a log10 scale, values above this value are rounded down to it (e.g. to prevent white blobs). zoom_around_mask: true # If True, plots of data structures with a mask automatically zoom in the masked region. - disable_zoom_for_fits: true # If True, the zoom-in around the masked region is disabled when outputting .fits files, which is useful to retain the same dimensions as the input data. inversion: reconstruction_vmax_factor: 0.5 total_mappings_pixels : 8 # The number of source pixels used when plotting the subplot_mappings of a pixelization. diff --git a/autoarray/mask/derive/zoom_2d.py b/autoarray/mask/derive/zoom_2d.py index cc9d4b8ac..bc987d555 100644 --- a/autoarray/mask/derive/zoom_2d.py +++ b/autoarray/mask/derive/zoom_2d.py @@ -2,8 +2,6 @@ import numpy as np from typing import List, Tuple, Union -from autoconf import conf - from autoarray.structures.grids import grid_2d_util diff --git a/autoarray/plot/mat_plot/two_d.py b/autoarray/plot/mat_plot/two_d.py index 0200537bb..b83ac6622 100644 --- a/autoarray/plot/mat_plot/two_d.py +++ b/autoarray/plot/mat_plot/two_d.py @@ -268,14 +268,6 @@ def plot_array( extent = array.geometry.extent - if ( - self.output.format == "fits" - and conf.instance["visualize"]["general"]["general"][ - "disable_zoom_for_fits" - ] - ): - zoom_around_mask = False - ax = None if not self.is_for_subplot: diff --git a/test_autoarray/config/visualize.yaml b/test_autoarray/config/visualize.yaml index 568a11349..de62db024 100644 --- a/test_autoarray/config/visualize.yaml +++ b/test_autoarray/config/visualize.yaml @@ -3,7 +3,6 @@ general: backend: default imshow_origin: upper zoom_around_mask: true - disable_zoom_for_fits: true # If True, the zoom-in around the masked region is disabled when outputting .fits files, which is useful to retain the same dimensions as the input data. include_2d: border: true mapper_image_plane_mesh_grid: false From 8dd8755553100e640b13bb00fd98fa77da3bb549 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Mon, 21 Apr 2025 09:34:07 +0100 Subject: [PATCH 05/13] aatempted docstrings but wait until internet for AI --- autoarray/mask/derive/zoom_2d.py | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/autoarray/mask/derive/zoom_2d.py b/autoarray/mask/derive/zoom_2d.py index bc987d555..d9ef7b418 100644 --- a/autoarray/mask/derive/zoom_2d.py +++ b/autoarray/mask/derive/zoom_2d.py @@ -8,7 +8,44 @@ class Zoom2D: def __init__(self, mask: Union[np.ndarray, List]): + """ + Derives a zoomed in `Mask2D` object from a `Mask2D` object, which is typically used to visualize 2D arrays + zoomed in to only the unmasked region an analysis is performed on. + + A `Mask2D` masks values which are associated with a uniform 2D rectangular grid of pixels, where unmasked + entries (which are `False`) are used in subsequent calculations and masked values (which are `True`) are + omitted (for a full description see the :meth:`Mask2D` class API + documentation `). + + The `Zoom2D` object calculations many different zoomed in qu + + Parameters + ---------- + mask + The `Mask2D` from which zoomed in `Mask2D` objects are derived. + + Examples + -------- + .. code-block:: python + + import autoarray as aa + + mask_2d = aa.Mask2D( + mask=[ + [True, True, True, True, True], + [True, False, False, False, True], + [True, False, False, False, True], + [True, False, False, False, True], + [True, True, True, True, True], + ], + pixel_scales=1.0, + ) + + zoom_2d = aa.Zoom2D(mask=mask_2d) + + print(zoom_2d.centre) + """ self.mask = mask @property From 093a7aab7e35710c99e780b19ec5c8ada3434721 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Mon, 21 Apr 2025 09:36:35 +0100 Subject: [PATCH 06/13] Array2D uses zoomed_around_mask --- autoarray/structures/arrays/uniform_2d.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/autoarray/structures/arrays/uniform_2d.py b/autoarray/structures/arrays/uniform_2d.py index 4406db105..c86205686 100644 --- a/autoarray/structures/arrays/uniform_2d.py +++ b/autoarray/structures/arrays/uniform_2d.py @@ -7,6 +7,7 @@ from autoconf.fitsable import ndarray_via_fits_from, header_obj_from from autoarray.mask.mask_2d import Mask2D +from autoarray.mask.derive.zoom_2d import Zoom2D from autoarray.structures.abstract_structure import Structure from autoarray.structures.header import Header from autoarray.structures.arrays.uniform_1d import Array1D @@ -463,12 +464,14 @@ def zoomed_around_mask(self, buffer: int = 1) -> "Array2D": The number pixels around the extracted array used as a buffer. """ + zoom = Zoom2D(mask=self.mask) + extracted_array_2d = array_2d_util.extracted_array_2d_from( array_2d=np.array(self.native), - y0=self.mask.zoom_region[0] - buffer, - y1=self.mask.zoom_region[1] + buffer, - x0=self.mask.zoom_region[2] - buffer, - x1=self.mask.zoom_region[3] + buffer, + y0=zoom.region[0] - buffer, + y1=zoom.region[1] + buffer, + x0=zoom.region[2] - buffer, + x1=zoom.region[3] + buffer, ) mask = Mask2D.all_false( From 8d0a157e3e32d8ef95d31585f2ac037617b4338b Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Mon, 21 Apr 2025 09:41:54 +0100 Subject: [PATCH 07/13] clean up zoom in Array2D --- autoarray/plot/mat_plot/two_d.py | 5 +--- autoarray/plot/wrap/two_d/array_overlay.py | 5 +++- autoarray/structures/arrays/uniform_2d.py | 30 ------------------- .../structures/arrays/test_uniform_2d.py | 4 ++- 4 files changed, 8 insertions(+), 36 deletions(-) diff --git a/autoarray/plot/mat_plot/two_d.py b/autoarray/plot/mat_plot/two_d.py index b83ac6622..3d5cc54f9 100644 --- a/autoarray/plot/mat_plot/two_d.py +++ b/autoarray/plot/mat_plot/two_d.py @@ -262,11 +262,8 @@ def plot_array( buffer = 0 if array.mask.is_all_false else 1 array = array.zoomed_around_mask(buffer=buffer) - extent = array.extent_of_zoomed_array(buffer=buffer) - else: - - extent = array.geometry.extent + extent = array.geometry.extent ax = None diff --git a/autoarray/plot/wrap/two_d/array_overlay.py b/autoarray/plot/wrap/two_d/array_overlay.py index 57652e8df..ef292d6c5 100644 --- a/autoarray/plot/wrap/two_d/array_overlay.py +++ b/autoarray/plot/wrap/two_d/array_overlay.py @@ -1,6 +1,7 @@ import matplotlib.pyplot as plt from autoarray.plot.wrap.two_d.abstract import AbstractMatWrap2D +from autoarray.mask.derive.zoom_2d import Zoom2D class ArrayOverlay(AbstractMatWrap2D): @@ -17,6 +18,8 @@ class ArrayOverlay(AbstractMatWrap2D): def overlay_array(self, array, figure): aspect = figure.aspect_from(shape_native=array.shape_native) - extent = array.extent_of_zoomed_array(buffer=0) + + array_zoom = array.zoomed_around_mask(buffer=0) + extent = array_zoom.geometry.extent plt.imshow(X=array.native, aspect=aspect, extent=extent, **self.config_dict) diff --git a/autoarray/structures/arrays/uniform_2d.py b/autoarray/structures/arrays/uniform_2d.py index c86205686..56a9cbe1f 100644 --- a/autoarray/structures/arrays/uniform_2d.py +++ b/autoarray/structures/arrays/uniform_2d.py @@ -486,36 +486,6 @@ def zoomed_around_mask(self, buffer: int = 1) -> "Array2D": return Array2D(values=array, mask=mask, header=self.header) - def extent_of_zoomed_array(self, buffer: int = 1) -> np.ndarray: - """ - For an extracted zoomed array computed from the method *zoomed_around_mask* compute its extent in scaled - coordinates. - - The extent of the grid in scaled units returned as an ``ndarray`` of the form [x_min, x_max, y_min, y_max]. - - This is used visualize zoomed and extracted arrays via the imshow() method. - - Parameters - ---------- - buffer - The number pixels around the extracted array used as a buffer. - """ - extracted_array_2d = array_2d_util.extracted_array_2d_from( - array_2d=np.array(self.native), - y0=self.mask.zoom_region[0] - buffer, - y1=self.mask.zoom_region[1] + buffer, - x0=self.mask.zoom_region[2] - buffer, - x1=self.mask.zoom_region[3] + buffer, - ) - - mask = Mask2D.all_false( - shape_native=extracted_array_2d.shape, - pixel_scales=self.pixel_scales, - origin=self.mask.mask_centre, - ) - - return mask.geometry.extent - def resized_from( self, new_shape: Tuple[int, int], mask_pad_value: int = 0.0 ) -> "Array2D": diff --git a/test_autoarray/structures/arrays/test_uniform_2d.py b/test_autoarray/structures/arrays/test_uniform_2d.py index 39cbc71df..c214caa7e 100644 --- a/test_autoarray/structures/arrays/test_uniform_2d.py +++ b/test_autoarray/structures/arrays/test_uniform_2d.py @@ -479,7 +479,9 @@ def test__extent_of_zoomed_array(): arr_masked = aa.Array2D(values=array_2d, mask=mask) - extent = arr_masked.extent_of_zoomed_array(buffer=1) + arr_zoom = arr_masked.zoomed_around_mask(buffer=1) + + extent = arr_zoom.geometry.extent assert extent == pytest.approx(np.array([-4.0, 6.0, -2.0, 3.0]), 1.0e-4) From a6d9d50539bb21c9c9b8d5e7b60f4047d95b3058 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Mon, 21 Apr 2025 09:43:18 +0100 Subject: [PATCH 08/13] clean up use of extent in zoom tests --- test_autoarray/plot/wrap/base/test_ticks.py | 6 ++-- .../structures/arrays/test_uniform_2d.py | 29 ------------------- 2 files changed, 4 insertions(+), 31 deletions(-) diff --git a/test_autoarray/plot/wrap/base/test_ticks.py b/test_autoarray/plot/wrap/base/test_ticks.py index ead11e885..dbbf41eeb 100644 --- a/test_autoarray/plot/wrap/base/test_ticks.py +++ b/test_autoarray/plot/wrap/base/test_ticks.py @@ -60,7 +60,8 @@ def test__yticks__set(): units = aplt.Units(use_scaled=True, ticks_convert_factor=None) yticks = aplt.YTicks(fontsize=34) - extent = array.extent_of_zoomed_array(buffer=1) + array_zoom = array.zoomed_around_mask(buffer=1) + extent = array_zoom.geometry.extent yticks.set(min_value=extent[2], max_value=extent[3], units=units) yticks = aplt.YTicks(fontsize=34) @@ -105,7 +106,8 @@ def test__xticks__set(): array = aa.Array2D.ones(shape_native=(2, 2), pixel_scales=1.0) units = aplt.Units(use_scaled=True, ticks_convert_factor=None) xticks = aplt.XTicks(fontsize=34) - extent = array.extent_of_zoomed_array(buffer=1) + array_zoom = array.zoomed_around_mask(buffer=1) + extent = array_zoom.geometry.extent xticks.set(min_value=extent[0], max_value=extent[1], units=units) xticks = aplt.XTicks(fontsize=34) diff --git a/test_autoarray/structures/arrays/test_uniform_2d.py b/test_autoarray/structures/arrays/test_uniform_2d.py index c214caa7e..6222d26a8 100644 --- a/test_autoarray/structures/arrays/test_uniform_2d.py +++ b/test_autoarray/structures/arrays/test_uniform_2d.py @@ -457,35 +457,6 @@ def test__zoomed_around_mask__origin_updated(): assert arr_zoomed.mask.origin == (0.0, 1.0) -def test__extent_of_zoomed_array(): - array_2d = [ - [1.0, 2.0, 3.0, 4.0], - [5.0, 6.0, 7.0, 8.0], - [9.0, 10.0, 11.0, 12.0], - [13.0, 14.0, 15.0, 16.0], - ] - - mask = aa.Mask2D( - mask=np.array( - [ - [True, True, True, False], - [True, False, False, True], - [True, False, False, True], - [True, True, True, True], - ] - ), - pixel_scales=(1.0, 2.0), - ) - - arr_masked = aa.Array2D(values=array_2d, mask=mask) - - arr_zoom = arr_masked.zoomed_around_mask(buffer=1) - - extent = arr_zoom.geometry.extent - - assert extent == pytest.approx(np.array([-4.0, 6.0, -2.0, 3.0]), 1.0e-4) - - def test__binned_across_rows(): array = aa.Array2D.no_mask(values=np.ones((4, 3)), pixel_scales=1.0) From 73fcacae914713e0e57dcb50f42eb4a0e8f5ec5d Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Mon, 21 Apr 2025 09:45:09 +0100 Subject: [PATCH 09/13] fix unit tes --- test_autoarray/fit/plot/test_fit_imaging_plotters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_autoarray/fit/plot/test_fit_imaging_plotters.py b/test_autoarray/fit/plot/test_fit_imaging_plotters.py index 4e5480ca7..31c288a4c 100644 --- a/test_autoarray/fit/plot/test_fit_imaging_plotters.py +++ b/test_autoarray/fit/plot/test_fit_imaging_plotters.py @@ -87,4 +87,4 @@ def test__output_as_fits__correct_output_format( file_path=path.join(plot_path, "data.fits"), hdu=0 ) - assert image_from_plot.shape == (7, 7) + assert image_from_plot.shape == (5, 5) From ddd8b74363d57f7b6b7057bf1719a676c19980c9 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Mon, 21 Apr 2025 12:07:18 +0100 Subject: [PATCH 10/13] docstrings and remove mask_unmasked --- autoarray/mask/derive/zoom_2d.py | 63 +++++++++++++++------- test_autoarray/mask/derive/test_zoom_2d.py | 38 ------------- 2 files changed, 45 insertions(+), 56 deletions(-) diff --git a/autoarray/mask/derive/zoom_2d.py b/autoarray/mask/derive/zoom_2d.py index d9ef7b418..75b507b57 100644 --- a/autoarray/mask/derive/zoom_2d.py +++ b/autoarray/mask/derive/zoom_2d.py @@ -50,6 +50,17 @@ def __init__(self, mask: Union[np.ndarray, List]): @property def centre(self) -> Tuple[float, float]: + """ + Returns the centre of the zoomed in region, which is the average of the maximum and minimum y and x pixel values + of the unmasked region. + + The y and x pixel values are the pixel coordinates of the unmasked region, which are derived from the + `Mask2D` object. The pixel coordinates are in the same units as the pixel scales of the `Mask2D` object. + + Returns + ------- + The centre of the zoomed in region. + """ from autoarray.structures.grids.uniform_2d import Grid2D grid = grid_2d_util.grid_2d_slim_via_mask_from( @@ -73,6 +84,17 @@ def centre(self) -> Tuple[float, float]: @property def offset_pixels(self) -> Tuple[float, float]: + """ + Returns the offset of the centred of the zoomed in region from the centre of the `Mask2D` object in pixel + units. + + This is computed by subtracting the pixel coordinates of the `Mask2D` object from the pixel coordinates of + the zoomed in region. + + Returns + ------- + The offset of the zoomed in region from the centre of the `Mask2D` object in pixel units. + """ if self.mask.pixel_scales is None: return self.mask.geometry.central_pixel_coordinates @@ -83,6 +105,17 @@ def offset_pixels(self) -> Tuple[float, float]: @property def offset_scaled(self) -> Tuple[float, float]: + """ + Returns the offset of the centred of the zoomed in region from the centre of the `Mask2D` object in scaled + units. + + This is computed by subtracting the pixel coordinates of the `Mask2D` object from the pixel coordinates of + the zoomed in region. + + Returns + ------- + The offset of the zoomed in region from the centre of the `Mask2D` object in scaled units. + """ return ( -self.mask.pixel_scales[0] * self.offset_pixels[0], self.mask.pixel_scales[1] * self.offset_pixels[1], @@ -91,10 +124,12 @@ def offset_scaled(self) -> Tuple[float, float]: @property def region(self) -> List[int]: """ - The zoomed rectangular region corresponding to the square encompassing all unmasked values. This zoomed - extraction region is a squuare, even if the mask is rectangular. + The zoomed region corresponding to the square encompassing all unmasked values. This is used to zoom in on the region of an image that is used in an analysis for visualization. + + This zoomed extraction region is a square, even if the mask is rectangular, so that extraction regions are + always squares which is important for ensuring visualization does not have aspect ratio issues. """ where = np.array(np.where(np.invert(self.mask.astype("bool")))) @@ -119,22 +154,14 @@ def region(self) -> List[int]: @property def shape_native(self) -> Tuple[int, int]: - region = self.region - return (region[1] - region[0], region[3] - region[2]) - - @property - def mask_unmasked(self) -> "Mask2D": """ - The scaled-grid of (y,x) coordinates of every pixel. + The shape of the zoomed in region in pixels. - This is defined from the top-left corner, such that the first pixel at location [0, 0] will have a negative x - value y value in scaled units. - """ - - from autoarray.mask.mask_2d import Mask2D + This is computed by subtracting the minimum and maximum y and x pixel values of the unmasked region. - return Mask2D.all_false( - shape_native=self.shape_native, - pixel_scales=self.mask.pixel_scales, - origin=self.offset_scaled, - ) \ No newline at end of file + Returns + ------- + The shape of the zoomed in region in pixels. + """ + region = self.region + return (region[1] - region[0], region[3] - region[2]) \ No newline at end of file diff --git a/test_autoarray/mask/derive/test_zoom_2d.py b/test_autoarray/mask/derive/test_zoom_2d.py index 35168f74e..4c380e037 100644 --- a/test_autoarray/mask/derive/test_zoom_2d.py +++ b/test_autoarray/mask/derive/test_zoom_2d.py @@ -109,41 +109,3 @@ def test__quantities(): assert zoom.centre == (6, 2) assert zoom.offset_pixels == (3, 1) - - -def test__mask_unmasked(): - mask = aa.Mask2D( - mask=np.array( - [ - [False, True, True, True], - [True, False, True, True], - [True, True, True, True], - ] - ), - pixel_scales=(1.0, 1.0), - ) - zoom = aa.Zoom2D(mask=mask) - - mask_unmasked = zoom.mask_unmasked - - assert (mask_unmasked == np.array([[False, False], [False, False]])).all() - assert mask_unmasked.origin == (0.5, -1.0) - - mask = aa.Mask2D( - mask=np.array( - [ - [False, True, True, True], - [True, False, True, True], - [True, False, True, True], - ] - ), - pixel_scales=(1.0, 2.0), - ) - zoom = aa.Zoom2D(mask=mask) - - mask_unmasked = zoom.mask_unmasked - - assert ( - mask_unmasked == np.array([[False, False], [False, False], [False, False]]) - ).all() - assert mask_unmasked.origin == (0.0, -2.0) \ No newline at end of file From 6b363da297b9dca23661f1e65e1a1feca22066bf Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Mon, 21 Apr 2025 12:21:19 +0100 Subject: [PATCH 11/13] added fits_are_Zoomed to config --- autoarray/config/visualize/plots.yaml | 3 +++ autoarray/structures/arrays/uniform_2d.py | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/autoarray/config/visualize/plots.yaml b/autoarray/config/visualize/plots.yaml index aa2664506..adf47212a 100644 --- a/autoarray/config/visualize/plots.yaml +++ b/autoarray/config/visualize/plots.yaml @@ -3,6 +3,9 @@ # For example, if `plots: fit: subplot_fit=True``, the ``fit_dataset.png`` subplot file will # be plotted every time visualization is performed. +subplot_format: [png] # Output format of all subplots, can be png, pdf or both (e.g. [png, pdf]) +fits_are_zoomed: true # If true, output .fits files are zoomed in on the center of the unmasked region image, saving hard-disk space. + dataset: # Settings for plots of all datasets (e.g. ImagingPlotter, InterferometerPlotter). subplot_dataset: true # Plot subplot containing all dataset quantities (e.g. the data, noise-map, etc.)? imaging: # Settings for plots of imaging datasets (e.g. ImagingPlotter) diff --git a/autoarray/structures/arrays/uniform_2d.py b/autoarray/structures/arrays/uniform_2d.py index 56a9cbe1f..df698619d 100644 --- a/autoarray/structures/arrays/uniform_2d.py +++ b/autoarray/structures/arrays/uniform_2d.py @@ -293,6 +293,25 @@ def native(self) -> "Array2D": values=self, mask=self.mask, header=self.header, store_native=True ) + @property + def native_for_fits(self) -> "Array2D": + """ + Return a `Array2D` for output to a .fits file, where the data is stored in its `native` representation, + which is an ``ndarray`` of shape [total_y_pixels, total_x_pixels]. + + Depending on configuration files, this array could be zoomed in on such that only the unmasked region + of the image is included in the .fits file, to save hard-disk space. Alternatively, the original `shape_native` + of the data can be retained. + + If it is already stored in its `native` representation it is return as it is. If not, it is mapped from + `slim` to `native` and returned as a new `Array2D`. + """ + + + return Array2D( + values=self, mask=self.mask, header=self.header, store_native=True + ) + @property def native_skip_mask(self) -> "Array2D": """ From ff27742e9a8b407354813c031545f42f3cabcf1c Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Mon, 21 Apr 2025 12:42:33 +0100 Subject: [PATCH 12/13] move zoom array to Zoom2D --- autoarray/mask/derive/zoom_2d.py | 42 ++++++- autoarray/plot/mat_plot/two_d.py | 5 +- autoarray/plot/wrap/two_d/array_overlay.py | 3 +- autoarray/structures/arrays/uniform_2d.py | 40 +------ test_autoarray/mask/derive/test_zoom_2d.py | 103 ++++++++++++++++++ test_autoarray/plot/wrap/base/test_ticks.py | 6 +- .../structures/arrays/test_uniform_2d.py | 103 ------------------ 7 files changed, 159 insertions(+), 143 deletions(-) diff --git a/autoarray/mask/derive/zoom_2d.py b/autoarray/mask/derive/zoom_2d.py index 75b507b57..387667db4 100644 --- a/autoarray/mask/derive/zoom_2d.py +++ b/autoarray/mask/derive/zoom_2d.py @@ -1,7 +1,11 @@ from __future__ import annotations import numpy as np -from typing import List, Tuple, Union +from typing import TYPE_CHECKING, List, Tuple, Union +if TYPE_CHECKING: + from autoarray.structures.arrays.uniform_2d import Array2D + +from autoarray.structures.arrays import array_2d_util from autoarray.structures.grids import grid_2d_util @@ -164,4 +168,38 @@ def shape_native(self) -> Tuple[int, int]: The shape of the zoomed in region in pixels. """ region = self.region - return (region[1] - region[0], region[3] - region[2]) \ No newline at end of file + return (region[1] - region[0], region[3] - region[2]) + + def array_2d_from(self, array : Array2D, buffer: int = 1) -> Array2D: + """ + Extract the 2D region of an array corresponding to the rectangle encompassing all unmasked values. + + This is used to extract and visualize only the region of an image that is used in an analysis. + + Parameters + ---------- + buffer + The number pixels around the extracted array used as a buffer. + """ + from autoarray.structures.arrays.uniform_2d import Array2D + from autoarray.mask.mask_2d import Mask2D + + extracted_array_2d = array_2d_util.extracted_array_2d_from( + array_2d=np.array(array.native), + y0=self.region[0] - buffer, + y1=self.region[1] + buffer, + x0=self.region[2] - buffer, + x1=self.region[3] + buffer, + ) + + mask = Mask2D.all_false( + shape_native=extracted_array_2d.shape, + pixel_scales=array.pixel_scales, + origin=array.mask.mask_centre, + ) + + arr = array_2d_util.convert_array_2d( + array_2d=extracted_array_2d, mask_2d=mask + ) + + return Array2D(values=arr, mask=mask, header=array.header) \ No newline at end of file diff --git a/autoarray/plot/mat_plot/two_d.py b/autoarray/plot/mat_plot/two_d.py index 3d5cc54f9..20b032bf3 100644 --- a/autoarray/plot/mat_plot/two_d.py +++ b/autoarray/plot/mat_plot/two_d.py @@ -12,6 +12,7 @@ from autoarray.plot.mat_plot.abstract import AbstractMatPlot from autoarray.plot.auto_labels import AutoLabels from autoarray.plot.visuals.two_d import Visuals2D +from autoarray.mask.derive.zoom_2d import Zoom2D from autoarray.structures.arrays.uniform_2d import Array2D from autoarray.structures.arrays import array_2d_util @@ -259,9 +260,11 @@ def plot_array( "zoom_around_mask" ]: + zoom = Zoom2D(mask=array.mask) + buffer = 0 if array.mask.is_all_false else 1 - array = array.zoomed_around_mask(buffer=buffer) + array = zoom.array_2d_from(array=array, buffer=buffer) extent = array.geometry.extent diff --git a/autoarray/plot/wrap/two_d/array_overlay.py b/autoarray/plot/wrap/two_d/array_overlay.py index ef292d6c5..372bb5f6c 100644 --- a/autoarray/plot/wrap/two_d/array_overlay.py +++ b/autoarray/plot/wrap/two_d/array_overlay.py @@ -19,7 +19,8 @@ class ArrayOverlay(AbstractMatWrap2D): def overlay_array(self, array, figure): aspect = figure.aspect_from(shape_native=array.shape_native) - array_zoom = array.zoomed_around_mask(buffer=0) + zoom = Zoom2D(mask=array.mask) + array_zoom = zoom.array_2d_from(array=array, buffer=0) extent = array_zoom.geometry.extent plt.imshow(X=array.native, aspect=aspect, extent=extent, **self.config_dict) diff --git a/autoarray/structures/arrays/uniform_2d.py b/autoarray/structures/arrays/uniform_2d.py index df698619d..8610bd3b4 100644 --- a/autoarray/structures/arrays/uniform_2d.py +++ b/autoarray/structures/arrays/uniform_2d.py @@ -306,7 +306,13 @@ def native_for_fits(self) -> "Array2D": If it is already stored in its `native` representation it is return as it is. If not, it is mapped from `slim` to `native` and returned as a new `Array2D`. """ + if conf.instance["visualize"]["plots"]["fits_are_zoomed"]: + zoom = Zoom2D(mask=self.mask) + + buffer = 0 if self.mask.is_all_false else 1 + + return zoom.array_2d_from(array=self, buffer=buffer) return Array2D( values=self, mask=self.mask, header=self.header, store_native=True @@ -471,40 +477,6 @@ def brightest_sub_pixel_coordinate_in_region_from( pixel_coordinates_2d=(subpixel_y, subpixel_x) ) - def zoomed_around_mask(self, buffer: int = 1) -> "Array2D": - """ - Extract the 2D region of an array corresponding to the rectangle encompassing all unmasked values. - - This is used to extract and visualize only the region of an image that is used in an analysis. - - Parameters - ---------- - buffer - The number pixels around the extracted array used as a buffer. - """ - - zoom = Zoom2D(mask=self.mask) - - extracted_array_2d = array_2d_util.extracted_array_2d_from( - array_2d=np.array(self.native), - y0=zoom.region[0] - buffer, - y1=zoom.region[1] + buffer, - x0=zoom.region[2] - buffer, - x1=zoom.region[3] + buffer, - ) - - mask = Mask2D.all_false( - shape_native=extracted_array_2d.shape, - pixel_scales=self.pixel_scales, - origin=self.mask.mask_centre, - ) - - array = array_2d_util.convert_array_2d( - array_2d=extracted_array_2d, mask_2d=mask - ) - - return Array2D(values=array, mask=mask, header=self.header) - def resized_from( self, new_shape: Tuple[int, int], mask_pad_value: int = 0.0 ) -> "Array2D": diff --git a/test_autoarray/mask/derive/test_zoom_2d.py b/test_autoarray/mask/derive/test_zoom_2d.py index 4c380e037..979b9d457 100644 --- a/test_autoarray/mask/derive/test_zoom_2d.py +++ b/test_autoarray/mask/derive/test_zoom_2d.py @@ -109,3 +109,106 @@ def test__quantities(): assert zoom.centre == (6, 2) assert zoom.offset_pixels == (3, 1) + + +def test__array_2d_from(): + array_2d = [ + [1.0, 2.0, 3.0, 4.0], + [5.0, 6.0, 7.0, 8.0], + [9.0, 10.0, 11.0, 12.0], + [13.0, 14.0, 15.0, 16.0], + ] + + mask = aa.Mask2D( + mask=[ + [True, True, True, True], + [True, False, False, True], + [True, False, False, True], + [True, True, True, True], + ], + pixel_scales=(1.0, 1.0), + ) + + arr = aa.Array2D(values=array_2d, mask=mask) + zoom = aa.Zoom2D(mask=mask) + arr_zoomed = zoom.array_2d_from(array=arr, buffer=0) + + assert (arr_zoomed.native == np.array([[6.0, 7.0], [10.0, 11.0]])).all() + + mask = aa.Mask2D( + mask=np.array( + [ + [True, True, True, True], + [True, False, False, True], + [False, False, False, True], + [True, True, True, True], + ] + ), + pixel_scales=(1.0, 1.0), + ) + + arr = aa.Array2D(values=array_2d, mask=mask) + zoom = aa.Zoom2D(mask=mask) + arr_zoomed = zoom.array_2d_from(array=arr, buffer=0) + + assert (arr_zoomed.native == np.array([[0.0, 6.0, 7.0], [9.0, 10.0, 11.0]])).all() + + mask = aa.Mask2D( + mask=np.array( + [ + [True, False, True, True], + [True, False, False, True], + [True, False, False, True], + [True, True, True, True], + ] + ), + pixel_scales=(1.0, 1.0), + ) + + arr = aa.Array2D(values=array_2d, mask=mask) + zoom = aa.Zoom2D(mask=mask) + arr_zoomed = zoom.array_2d_from(array=arr, buffer=0) + + assert (arr_zoomed.native == np.array([[2.0, 0.0], [6.0, 7.0], [10.0, 11.0]])).all() + + array_2d = np.ones(shape=(4, 4)) + + mask = aa.Mask2D( + mask=np.array( + [ + [True, True, True, True], + [True, False, False, True], + [True, False, False, True], + [True, True, True, True], + ] + ), + pixel_scales=(1.0, 1.0), + ) + + arr = aa.Array2D(values=array_2d, mask=mask) + zoom = aa.Zoom2D(mask=mask) + arr_zoomed = zoom.array_2d_from(array=arr, buffer=0) + + assert arr_zoomed.mask.origin == (0.0, 0.0) + + array_2d = np.ones(shape=(6, 6)) + + mask = aa.Mask2D( + mask=np.array( + [ + [True, True, True, True, True, True], + [True, True, True, True, True, True], + [True, True, True, False, False, True], + [True, True, True, False, False, True], + [True, True, True, True, True, True], + [True, True, True, True, True, True], + ] + ), + pixel_scales=(1.0, 1.0), + ) + + arr = aa.Array2D(values=array_2d, mask=mask) + zoom = aa.Zoom2D(mask=mask) + arr_zoomed = zoom.array_2d_from(array=arr, buffer=0) + + assert arr_zoomed.mask.origin == (0.0, 1.0) diff --git a/test_autoarray/plot/wrap/base/test_ticks.py b/test_autoarray/plot/wrap/base/test_ticks.py index dbbf41eeb..ab9021eec 100644 --- a/test_autoarray/plot/wrap/base/test_ticks.py +++ b/test_autoarray/plot/wrap/base/test_ticks.py @@ -60,7 +60,8 @@ def test__yticks__set(): units = aplt.Units(use_scaled=True, ticks_convert_factor=None) yticks = aplt.YTicks(fontsize=34) - array_zoom = array.zoomed_around_mask(buffer=1) + zoom = aa.Zoom2D(mask=array.mask) + array_zoom = zoom.array_2d_from(array=array, buffer=0) extent = array_zoom.geometry.extent yticks.set(min_value=extent[2], max_value=extent[3], units=units) @@ -106,7 +107,8 @@ def test__xticks__set(): array = aa.Array2D.ones(shape_native=(2, 2), pixel_scales=1.0) units = aplt.Units(use_scaled=True, ticks_convert_factor=None) xticks = aplt.XTicks(fontsize=34) - array_zoom = array.zoomed_around_mask(buffer=1) + zoom = aa.Zoom2D(mask=array.mask) + array_zoom = zoom.array_2d_from(array=array, buffer=0) extent = array_zoom.geometry.extent xticks.set(min_value=extent[0], max_value=extent[1], units=units) diff --git a/test_autoarray/structures/arrays/test_uniform_2d.py b/test_autoarray/structures/arrays/test_uniform_2d.py index 6222d26a8..457370b93 100644 --- a/test_autoarray/structures/arrays/test_uniform_2d.py +++ b/test_autoarray/structures/arrays/test_uniform_2d.py @@ -354,109 +354,6 @@ def test__trimmed_after_convolution_from(): ).all() assert new_arr.mask.pixel_scales == (1.0, 1.0) - -def test__zoomed_around_mask(): - array_2d = [ - [1.0, 2.0, 3.0, 4.0], - [5.0, 6.0, 7.0, 8.0], - [9.0, 10.0, 11.0, 12.0], - [13.0, 14.0, 15.0, 16.0], - ] - - mask = aa.Mask2D( - mask=[ - [True, True, True, True], - [True, False, False, True], - [True, False, False, True], - [True, True, True, True], - ], - pixel_scales=(1.0, 1.0), - ) - - arr_masked = aa.Array2D(values=array_2d, mask=mask) - - arr_zoomed = arr_masked.zoomed_around_mask(buffer=0) - - assert (arr_zoomed.native == np.array([[6.0, 7.0], [10.0, 11.0]])).all() - - mask = aa.Mask2D( - mask=np.array( - [ - [True, True, True, True], - [True, False, False, True], - [False, False, False, True], - [True, True, True, True], - ] - ), - pixel_scales=(1.0, 1.0), - ) - - arr_masked = aa.Array2D(values=array_2d, mask=mask) - arr_zoomed = arr_masked.zoomed_around_mask(buffer=0) - - assert (arr_zoomed.native == np.array([[0.0, 6.0, 7.0], [9.0, 10.0, 11.0]])).all() - - mask = aa.Mask2D( - mask=np.array( - [ - [True, False, True, True], - [True, False, False, True], - [True, False, False, True], - [True, True, True, True], - ] - ), - pixel_scales=(1.0, 1.0), - ) - - arr_masked = aa.Array2D(values=array_2d, mask=mask) - arr_zoomed = arr_masked.zoomed_around_mask(buffer=0) - assert (arr_zoomed.native == np.array([[2.0, 0.0], [6.0, 7.0], [10.0, 11.0]])).all() - - -def test__zoomed_around_mask__origin_updated(): - array_2d = np.ones(shape=(4, 4)) - - mask = aa.Mask2D( - mask=np.array( - [ - [True, True, True, True], - [True, False, False, True], - [True, False, False, True], - [True, True, True, True], - ] - ), - pixel_scales=(1.0, 1.0), - ) - - arr_masked = aa.Array2D(values=array_2d, mask=mask) - - arr_zoomed = arr_masked.zoomed_around_mask(buffer=0) - - assert arr_zoomed.mask.origin == (0.0, 0.0) - - array_2d = np.ones(shape=(6, 6)) - - mask = aa.Mask2D( - mask=np.array( - [ - [True, True, True, True, True, True], - [True, True, True, True, True, True], - [True, True, True, False, False, True], - [True, True, True, False, False, True], - [True, True, True, True, True, True], - [True, True, True, True, True, True], - ] - ), - pixel_scales=(1.0, 1.0), - ) - - arr_masked = aa.Array2D(values=array_2d, mask=mask) - - arr_zoomed = arr_masked.zoomed_around_mask(buffer=0) - - assert arr_zoomed.mask.origin == (0.0, 1.0) - - def test__binned_across_rows(): array = aa.Array2D.no_mask(values=np.ones((4, 3)), pixel_scales=1.0) From c4bca0e33c74b4eee3db9a7867f08f3008b29a86 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Tue, 29 Apr 2025 18:29:01 +0100 Subject: [PATCH 13/13] add new functions to Zoom2D --- autoarray/geometry/geometry_util.py | 4 +- autoarray/inversion/inversion/abstract.py | 12 +-- .../inversion/inversion/imaging/abstract.py | 16 ++-- .../pixelization/border_relocator.py | 12 +-- .../inversion/pixelization/mesh/mesh_util.py | 2 +- .../inversion/plot/inversion_plotters.py | 7 +- autoarray/mask/derive/zoom_2d.py | 82 +++++++++++++++++-- autoarray/mask/mask_1d_util.py | 2 +- autoarray/mask/mask_2d_util.py | 4 +- autoarray/operators/convolver.py | 30 +++---- .../over_sampling/over_sample_util.py | 6 +- autoarray/plot/mat_plot/two_d.py | 4 +- autoarray/plot/wrap/base/colorbar.py | 6 +- autoarray/structures/arrays/uniform_2d.py | 2 +- autoarray/structures/grids/grid_2d_util.py | 4 +- test_autoarray/mask/derive/test_zoom_2d.py | 14 ++-- test_autoarray/mask/test_mask_2d.py | 1 + .../structures/arrays/test_uniform_2d.py | 1 + 18 files changed, 136 insertions(+), 73 deletions(-) diff --git a/autoarray/geometry/geometry_util.py b/autoarray/geometry/geometry_util.py index 44c71a977..fad238ca5 100644 --- a/autoarray/geometry/geometry_util.py +++ b/autoarray/geometry/geometry_util.py @@ -182,7 +182,7 @@ def convert_pixel_scales_2d(pixel_scales: ty.PixelScales) -> Tuple[float, float] @numba_util.jit() def central_pixel_coordinates_2d_from( - shape_native: Tuple[int, int] + shape_native: Tuple[int, int], ) -> Tuple[float, float]: """ Returns the central pixel coordinates of a 2D geometry (and therefore a 2D data structure like an ``Array2D``) @@ -737,7 +737,7 @@ def grid_pixel_centres_2d_from( def extent_symmetric_from( - extent: Tuple[float, float, float, float] + extent: Tuple[float, float, float, float], ) -> Tuple[float, float, float, float]: """ Given an input extent of the form (x_min, x_max, y_min, y_max), this function returns an extent which is diff --git a/autoarray/inversion/inversion/abstract.py b/autoarray/inversion/inversion/abstract.py index 685a152c2..ecc1fd02d 100644 --- a/autoarray/inversion/inversion/abstract.py +++ b/autoarray/inversion/inversion/abstract.py @@ -509,12 +509,12 @@ def reconstruction(self) -> np.ndarray: solutions = np.zeros(np.shape(self.curvature_reg_matrix)[0]) - solutions[ - values_to_solve - ] = inversion_util.reconstruction_positive_only_from( - data_vector=data_vector_input, - curvature_reg_matrix=curvature_reg_matrix_input, - settings=self.settings, + solutions[values_to_solve] = ( + inversion_util.reconstruction_positive_only_from( + data_vector=data_vector_input, + curvature_reg_matrix=curvature_reg_matrix_input, + settings=self.settings, + ) ) return solutions else: diff --git a/autoarray/inversion/inversion/imaging/abstract.py b/autoarray/inversion/inversion/imaging/abstract.py index 5eccfa7d1..d6fe6f61a 100644 --- a/autoarray/inversion/inversion/imaging/abstract.py +++ b/autoarray/inversion/inversion/imaging/abstract.py @@ -104,11 +104,13 @@ def operated_mapping_matrix_list(self) -> List[np.ndarray]: """ return [ - self.convolver.convolve_mapping_matrix( - mapping_matrix=linear_obj.mapping_matrix + ( + self.convolver.convolve_mapping_matrix( + mapping_matrix=linear_obj.mapping_matrix + ) + if linear_obj.operated_mapping_matrix_override is None + else self.linear_func_operated_mapping_matrix_dict[linear_obj] ) - if linear_obj.operated_mapping_matrix_override is None - else self.linear_func_operated_mapping_matrix_dict[linear_obj] for linear_obj in self.linear_obj_list ] @@ -156,9 +158,9 @@ def linear_func_operated_mapping_matrix_dict(self) -> Dict: mapping_matrix=linear_func.mapping_matrix ) - linear_func_operated_mapping_matrix_dict[ - linear_func - ] = operated_mapping_matrix + linear_func_operated_mapping_matrix_dict[linear_func] = ( + operated_mapping_matrix + ) return linear_func_operated_mapping_matrix_dict diff --git a/autoarray/inversion/pixelization/border_relocator.py b/autoarray/inversion/pixelization/border_relocator.py index 50acf10e1..73a8d0d28 100644 --- a/autoarray/inversion/pixelization/border_relocator.py +++ b/autoarray/inversion/pixelization/border_relocator.py @@ -117,12 +117,12 @@ def sub_border_pixel_slim_indexes_from( int(border_pixel) ] - sub_border_pixels[ - border_1d_index - ] = grid_2d_util.furthest_grid_2d_slim_index_from( - grid_2d_slim=sub_grid_2d_slim, - slim_indexes=sub_border_pixels_of_border_pixel, - coordinate=mask_centre, + sub_border_pixels[border_1d_index] = ( + grid_2d_util.furthest_grid_2d_slim_index_from( + grid_2d_slim=sub_grid_2d_slim, + slim_indexes=sub_border_pixels_of_border_pixel, + coordinate=mask_centre, + ) ) return sub_border_pixels diff --git a/autoarray/inversion/pixelization/mesh/mesh_util.py b/autoarray/inversion/pixelization/mesh/mesh_util.py index 78cb4a860..305b56b72 100644 --- a/autoarray/inversion/pixelization/mesh/mesh_util.py +++ b/autoarray/inversion/pixelization/mesh/mesh_util.py @@ -7,7 +7,7 @@ @numba_util.jit() def rectangular_neighbors_from( - shape_native: Tuple[int, int] + shape_native: Tuple[int, int], ) -> Tuple[np.ndarray, np.ndarray]: """ Returns the 4 (or less) adjacent neighbors of every pixel on a rectangular pixelization as an ndarray of shape diff --git a/autoarray/inversion/plot/inversion_plotters.py b/autoarray/inversion/plot/inversion_plotters.py index 098769e80..98cede938 100644 --- a/autoarray/inversion/plot/inversion_plotters.py +++ b/autoarray/inversion/plot/inversion_plotters.py @@ -215,10 +215,9 @@ def figures_2d_of_pixelization( "inversion" ]["reconstruction_vmax_factor"] - self.mat_plot_2d.cmap.kwargs[ - "vmax" - ] = reconstruction_vmax_factor * np.max( - self.inversion.reconstruction + self.mat_plot_2d.cmap.kwargs["vmax"] = ( + reconstruction_vmax_factor + * np.max(self.inversion.reconstruction) ) vmax_custom = True diff --git a/autoarray/mask/derive/zoom_2d.py b/autoarray/mask/derive/zoom_2d.py index 387667db4..69c49b7cd 100644 --- a/autoarray/mask/derive/zoom_2d.py +++ b/autoarray/mask/derive/zoom_2d.py @@ -4,6 +4,7 @@ if TYPE_CHECKING: from autoarray.structures.arrays.uniform_2d import Array2D + from autoarray.mask.mask_2d import Mask2D from autoarray.structures.arrays import array_2d_util from autoarray.structures.grids import grid_2d_util @@ -18,7 +19,7 @@ def __init__(self, mask: Union[np.ndarray, List]): A `Mask2D` masks values which are associated with a uniform 2D rectangular grid of pixels, where unmasked entries (which are `False`) are used in subsequent calculations and masked values (which are `True`) are - omitted (for a full description see the :meth:`Mask2D` class API + omitted (for a full description see the :meth:`Mask2D` class API documentation `). The `Zoom2D` object calculations many different zoomed in qu @@ -170,7 +171,66 @@ def shape_native(self) -> Tuple[int, int]: region = self.region return (region[1] - region[0], region[3] - region[2]) - def array_2d_from(self, array : Array2D, buffer: int = 1) -> Array2D: + def extent_from(self, buffer: int = 1) -> np.ndarray: + """ + For an extracted zoomed array computed from the method *zoomed_around_mask* compute its extent in scaled + coordinates. + + The extent of the grid in scaled units returned as an ``ndarray`` of the form [x_min, x_max, y_min, y_max]. + + This is used visualize zoomed and extracted arrays via the imshow() method. + + Parameters + ---------- + buffer + The number pixels around the extracted array used as a buffer. + """ + from autoarray.mask.mask_2d import Mask2D + + extracted_array_2d = array_2d_util.extracted_array_2d_from( + array_2d=np.array(self.mask), + y0=self.region[0] - buffer, + y1=self.region[1] + buffer, + x0=self.region[2] - buffer, + x1=self.region[3] + buffer, + ) + + mask = Mask2D.all_false( + shape_native=extracted_array_2d.shape, + pixel_scales=self.mask.pixel_scales, + origin=self.centre, + ) + + return mask.geometry.extent + + def mask_2d_from(self, buffer: int = 1) -> "Mask2D": + """ + Extract the 2D region of a mask corresponding to the rectangle encompassing all unmasked values. + + This is used to extract and visualize only the region of an image that is used in an analysis. + + Parameters + ---------- + buffer + The number pixels around the extracted array used as a buffer. + """ + from autoarray.mask.mask_2d import Mask2D + + extracted_mask_2d = array_2d_util.extracted_array_2d_from( + array_2d=np.array(self.mask), + y0=self.region[0] - buffer, + y1=self.region[1] + buffer, + x0=self.region[2] - buffer, + x1=self.region[3] + buffer, + ) + + return Mask2D( + mask=extracted_mask_2d, + pixel_scales=self.mask.pixel_scales, + origin=self.mask.origin, + ) + + def array_2d_from(self, array: Array2D, buffer: int = 1) -> Array2D: """ Extract the 2D region of an array corresponding to the rectangle encompassing all unmasked values. @@ -192,14 +252,20 @@ def array_2d_from(self, array : Array2D, buffer: int = 1) -> Array2D: x1=self.region[3] + buffer, ) - mask = Mask2D.all_false( - shape_native=extracted_array_2d.shape, + extracted_mask_2d = array_2d_util.extracted_array_2d_from( + array_2d=np.array(self.mask), + y0=self.region[0] - buffer, + y1=self.region[1] + buffer, + x0=self.region[2] - buffer, + x1=self.region[3] + buffer, + ) + + mask = Mask2D( + mask=extracted_mask_2d, pixel_scales=array.pixel_scales, origin=array.mask.mask_centre, ) - arr = array_2d_util.convert_array_2d( - array_2d=extracted_array_2d, mask_2d=mask - ) + arr = array_2d_util.convert_array_2d(array_2d=extracted_array_2d, mask_2d=mask) - return Array2D(values=arr, mask=mask, header=array.header) \ No newline at end of file + return Array2D(values=arr, mask=mask, header=array.header) diff --git a/autoarray/mask/mask_1d_util.py b/autoarray/mask/mask_1d_util.py index 4a47bbc70..3d9943c19 100644 --- a/autoarray/mask/mask_1d_util.py +++ b/autoarray/mask/mask_1d_util.py @@ -80,4 +80,4 @@ def native_index_for_slim_index_1d_from( native_index_for_slim_index_1d[slim_index] = x slim_index += 1 - return native_index_for_slim_index_1d \ No newline at end of file + return native_index_for_slim_index_1d diff --git a/autoarray/mask/mask_2d_util.py b/autoarray/mask/mask_2d_util.py index db2751b04..47db2413b 100644 --- a/autoarray/mask/mask_2d_util.py +++ b/autoarray/mask/mask_2d_util.py @@ -316,9 +316,7 @@ def elliptical_radius_from( y_scaled_elliptical = r_scaled * np.sin(theta_rotated) x_scaled_elliptical = r_scaled * np.cos(theta_rotated) - return np.sqrt( - x_scaled_elliptical**2.0 + (y_scaled_elliptical / axis_ratio) ** 2.0 - ) + return np.sqrt(x_scaled_elliptical**2.0 + (y_scaled_elliptical / axis_ratio) ** 2.0) @numba_util.jit() diff --git a/autoarray/operators/convolver.py b/autoarray/operators/convolver.py index c963311a2..1e1cb07fa 100644 --- a/autoarray/operators/convolver.py +++ b/autoarray/operators/convolver.py @@ -215,12 +215,12 @@ def __init__(self, mask, kernel): mask_index_array=self.mask_index_array, kernel_2d=np.array(self.kernel.native[:, :]), ) - self.image_frame_1d_indexes[ - mask_1d_index, : - ] = image_frame_1d_indexes - self.image_frame_1d_kernels[ - mask_1d_index, : - ] = image_frame_1d_kernels + self.image_frame_1d_indexes[mask_1d_index, :] = ( + image_frame_1d_indexes + ) + self.image_frame_1d_kernels[mask_1d_index, :] = ( + image_frame_1d_kernels + ) self.image_frame_1d_lengths[mask_1d_index] = image_frame_1d_indexes[ image_frame_1d_indexes >= 0 ].shape[0] @@ -257,15 +257,15 @@ def __init__(self, mask, kernel): mask_index_array=np.array(self.mask_index_array), kernel_2d=np.array(self.kernel.native), ) - self.blurring_frame_1d_indexes[ - mask_1d_index, : - ] = image_frame_1d_indexes - self.blurring_frame_1d_kernels[ - mask_1d_index, : - ] = image_frame_1d_kernels - self.blurring_frame_1d_lengths[ - mask_1d_index - ] = image_frame_1d_indexes[image_frame_1d_indexes >= 0].shape[0] + self.blurring_frame_1d_indexes[mask_1d_index, :] = ( + image_frame_1d_indexes + ) + self.blurring_frame_1d_kernels[mask_1d_index, :] = ( + image_frame_1d_kernels + ) + self.blurring_frame_1d_lengths[mask_1d_index] = ( + image_frame_1d_indexes[image_frame_1d_indexes >= 0].shape[0] + ) mask_1d_index += 1 @staticmethod diff --git a/autoarray/operators/over_sampling/over_sample_util.py b/autoarray/operators/over_sampling/over_sample_util.py index 1d4637fc6..6aca49925 100644 --- a/autoarray/operators/over_sampling/over_sample_util.py +++ b/autoarray/operators/over_sampling/over_sample_util.py @@ -258,9 +258,9 @@ def sub_slim_index_for_sub_native_index_from(sub_mask_2d: np.ndarray): for sub_mask_y in range(sub_mask_2d.shape[0]): for sub_mask_x in range(sub_mask_2d.shape[1]): if sub_mask_2d[sub_mask_y, sub_mask_x] == False: - sub_slim_index_for_sub_native_index[ - sub_mask_y, sub_mask_x - ] = sub_mask_1d_index + sub_slim_index_for_sub_native_index[sub_mask_y, sub_mask_x] = ( + sub_mask_1d_index + ) sub_mask_1d_index += 1 return sub_slim_index_for_sub_native_index diff --git a/autoarray/plot/mat_plot/two_d.py b/autoarray/plot/mat_plot/two_d.py index 20b032bf3..e000a91a2 100644 --- a/autoarray/plot/mat_plot/two_d.py +++ b/autoarray/plot/mat_plot/two_d.py @@ -256,9 +256,7 @@ def plot_array( "a pixel scales attribute." ) - if conf.instance["visualize"]["general"]["general"][ - "zoom_around_mask" - ]: + if conf.instance["visualize"]["general"]["general"]["zoom_around_mask"]: zoom = Zoom2D(mask=array.mask) diff --git a/autoarray/plot/wrap/base/colorbar.py b/autoarray/plot/wrap/base/colorbar.py index 272d93fcd..b0650013a 100644 --- a/autoarray/plot/wrap/base/colorbar.py +++ b/autoarray/plot/wrap/base/colorbar.py @@ -130,9 +130,9 @@ def tick_labels_from( cb_unit = units.colorbar_label middle_index = (len(manual_tick_labels) - 1) // 2 - manual_tick_labels[ - middle_index - ] = rf"{manual_tick_labels[middle_index]}{cb_unit}" + manual_tick_labels[middle_index] = ( + rf"{manual_tick_labels[middle_index]}{cb_unit}" + ) return manual_tick_labels diff --git a/autoarray/structures/arrays/uniform_2d.py b/autoarray/structures/arrays/uniform_2d.py index 8610bd3b4..e9e68bcf3 100644 --- a/autoarray/structures/arrays/uniform_2d.py +++ b/autoarray/structures/arrays/uniform_2d.py @@ -312,7 +312,7 @@ def native_for_fits(self) -> "Array2D": buffer = 0 if self.mask.is_all_false else 1 - return zoom.array_2d_from(array=self, buffer=buffer) + return zoom.array_2d_from(array=self, buffer=buffer).native return Array2D( values=self, mask=self.mask, header=self.header, store_native=True diff --git a/autoarray/structures/grids/grid_2d_util.py b/autoarray/structures/grids/grid_2d_util.py index 764f749b0..f21ff3db8 100644 --- a/autoarray/structures/grids/grid_2d_util.py +++ b/autoarray/structures/grids/grid_2d_util.py @@ -750,9 +750,7 @@ def grid_2d_slim_upscaled_from( The pixel scale of the uniform grid that laid over the irregular grid of (y,x) coordinates. """ - grid_2d_slim_upscaled = np.zeros( - shape=(grid_slim.shape[0] * upscale_factor**2, 2) - ) + grid_2d_slim_upscaled = np.zeros(shape=(grid_slim.shape[0] * upscale_factor**2, 2)) upscale_index = 0 diff --git a/test_autoarray/mask/derive/test_zoom_2d.py b/test_autoarray/mask/derive/test_zoom_2d.py index 979b9d457..dcf902bad 100644 --- a/test_autoarray/mask/derive/test_zoom_2d.py +++ b/test_autoarray/mask/derive/test_zoom_2d.py @@ -7,24 +7,24 @@ def test__quantities(): mask = aa.Mask2D.all_false(shape_native=(4, 6), pixel_scales=(1.0, 1.0)) zoom = aa.Zoom2D(mask=mask) - + assert zoom.centre == (1.5, 2.5) assert zoom.offset_pixels == (0, 0) assert zoom.shape_native == (6, 6) mask = aa.Mask2D.all_false(shape_native=(6, 4), pixel_scales=(1.0, 1.0)) zoom = aa.Zoom2D(mask=mask) - + assert zoom.centre == (2.5, 1.5) assert zoom.offset_pixels == (0, 0) assert zoom.shape_native == (6, 6) - + mask = aa.Mask2D( mask=np.array([[True, True, True], [True, True, False], [True, True, True]]), pixel_scales=(1.0, 1.0), ) zoom = aa.Zoom2D(mask=mask) - + assert zoom.centre == (1, 2) assert zoom.offset_pixels == (0, 1) assert zoom.shape_native == (1, 1) @@ -34,7 +34,7 @@ def test__quantities(): pixel_scales=(1.0, 1.0), ) zoom = aa.Zoom2D(mask=mask) - + assert zoom.centre == (2, 1) assert zoom.offset_pixels == (1, 0) assert zoom.shape_native == (1, 1) @@ -44,7 +44,7 @@ def test__quantities(): pixel_scales=(1.0, 1.0), ) zoom = aa.Zoom2D(mask=mask) - + assert zoom.centre == (0, 1) assert zoom.offset_pixels == (-1, 0) assert zoom.shape_native == (3, 3) @@ -54,7 +54,7 @@ def test__quantities(): pixel_scales=(1.0, 1.0), ) zoom = aa.Zoom2D(mask=mask) - + assert zoom.centre == (0, 0.5) assert zoom.offset_pixels == (-1, -0.5) assert zoom.shape_native == (1, 2) diff --git a/test_autoarray/mask/test_mask_2d.py b/test_autoarray/mask/test_mask_2d.py index 9d71c2929..ce902f545 100644 --- a/test_autoarray/mask/test_mask_2d.py +++ b/test_autoarray/mask/test_mask_2d.py @@ -526,6 +526,7 @@ def test__resized_from(): assert (mask_resized == mask_resized_manual).all() + def test__mask_centre(): mask = np.array( [ diff --git a/test_autoarray/structures/arrays/test_uniform_2d.py b/test_autoarray/structures/arrays/test_uniform_2d.py index 457370b93..0c55b0c38 100644 --- a/test_autoarray/structures/arrays/test_uniform_2d.py +++ b/test_autoarray/structures/arrays/test_uniform_2d.py @@ -354,6 +354,7 @@ def test__trimmed_after_convolution_from(): ).all() assert new_arr.mask.pixel_scales == (1.0, 1.0) + def test__binned_across_rows(): array = aa.Array2D.no_mask(values=np.ones((4, 3)), pixel_scales=1.0)