Skip to content

Commit

Permalink
check_adapt_background_pixels imeplmented
Browse files Browse the repository at this point in the history
  • Loading branch information
Jammy2211 committed Jan 14, 2024
1 parent 70765ca commit 51d87a0
Show file tree
Hide file tree
Showing 10 changed files with 177 additions and 48 deletions.
26 changes: 24 additions & 2 deletions autoarray/inversion/inversion/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ def __init__(
use_w_tilde_numpy: bool = False,
use_source_loop: bool = False,
use_linear_operators: bool = False,
image_mesh_min_mesh_pixels_per_pixel = None,
image_mesh_min_mesh_number : int = 5,
image_mesh_min_mesh_pixels_per_pixel=None,
image_mesh_min_mesh_number: int = 5,
image_mesh_adapt_background_percent_threshold: float = None,
image_mesh_adapt_background_percent_check: float = 0.1,
tolerance: float = 1e-8,
maxiter: int = 250,
):
Expand Down Expand Up @@ -55,6 +57,19 @@ def __init__(
use_linear_operators
For an interferometer inversion, whether to use the linear operator solution to solve the linear system
or not (this input does nothing for dataset data).
image_mesh_min_mesh_pixels_per_pixel
If not None, the image-mesh must place this many mesh pixels per image pixels in the N highest weighted
regions of the adapt data, or an `InversionException` is raised. This can be used to force the image-mesh
to cluster large numbers of source pixels to the adapt-datas brightest regions.
image_mesh_min_mesh_number
The value N given above in the docstring for `image_mesh_min_mesh_pixels_per_pixel`, indicating how many
image pixels are checked for having a threshold number of mesh pixels.
image_mesh_adapt_background_percent_threshold
If not None, the image-mesh must place this percentage of mesh-pixels in the background regions of the
`adapt_data`, where the background is the `image_mesh_adapt_background_percent_check` masked data pixels
with the lowest values.
image_mesh_adapt_background_percent_check
The percentage of masked data pixels which are checked for the background criteria.
tolerance
For an interferometer inversion using the linear operators method, sets the tolerance of the solver
(this input does nothing for dataset data and other interferometer methods).
Expand All @@ -76,6 +91,13 @@ def __init__(
)
self.image_mesh_min_mesh_pixels_per_pixel = image_mesh_min_mesh_pixels_per_pixel
self.image_mesh_min_mesh_number = image_mesh_min_mesh_number
self.image_mesh_adapt_background_percent_threshold = (
image_mesh_adapt_background_percent_threshold
)
self.image_mesh_adapt_background_percent_check = (
image_mesh_adapt_background_percent_check
)

self.tolerance = tolerance
self.maxiter = maxiter
self.use_w_tilde_numpy = use_w_tilde_numpy
Expand Down
2 changes: 1 addition & 1 deletion autoarray/inversion/mock/mock_image_mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def __init__(self, image_plane_mesh_grid=None):
self.image_plane_mesh_grid = image_plane_mesh_grid

def image_plane_mesh_grid_from(
self, grid: Grid2D, adapt_data: Optional[np.ndarray], settings = None
self, grid: Grid2D, adapt_data: Optional[np.ndarray], settings=None
) -> Grid2DIrregular:
if adapt_data is not None and self.image_plane_mesh_grid is not None:
return adapt_data * self.image_plane_mesh_grid
Expand Down
82 changes: 74 additions & 8 deletions autoarray/inversion/pixelization/image_mesh/abstract.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import Optional

import copy
import numpy as np

from autoarray.structures.arrays.uniform_2d import Array2D
Expand All @@ -10,6 +11,7 @@

from autoarray import exc


class AbstractImageMesh:
def __init__(self):
"""
Expand Down Expand Up @@ -49,7 +51,7 @@ def weight_map_from(self, adapt_data: np.ndarray):
return weight_map

def image_plane_mesh_grid_from(
self, grid: Grid2D, adapt_data: Optional[np.ndarray] = None, settings = None
self, grid: Grid2D, adapt_data: Optional[np.ndarray] = None, settings=None
) -> Grid2DIrregular:
raise NotImplementedError

Expand Down Expand Up @@ -102,7 +104,7 @@ def check_mesh_pixels_per_image_pixels(self, grid, mesh_grid, settings):
the number of mesh pixels in the 5 data pixels with the most data pixels is computed.
3) Compare the lowest value above to the value `settings.image_mesh_min_mesh_pixels_per_pixel`. If the value is
below this value, raise an `InversionException`.
Therefore, by settings `settings.image_mesh_min_mesh_pixels_per_pixel` to a value above 1 the code is forced
to adapt the image mesh enough to put many mesh pixels in the brightest image pixels.
Expand All @@ -121,16 +123,80 @@ def check_mesh_pixels_per_image_pixels(self, grid, mesh_grid, settings):

if settings is not None:
if settings.image_mesh_min_mesh_pixels_per_pixel is not None:

mesh_pixels_per_image_pixels = self.mesh_pixels_per_image_pixels_from(
grid=grid,
mesh_grid=mesh_grid
grid=grid, mesh_grid=mesh_grid
)

indices_of_highest_values = np.argsort(mesh_pixels_per_image_pixels)[-settings.image_mesh_min_mesh_number:]
lowest_mesh_pixels = np.min(mesh_pixels_per_image_pixels[indices_of_highest_values])
indices_of_highest_values = np.argsort(mesh_pixels_per_image_pixels)[
-settings.image_mesh_min_mesh_number :
]
lowest_mesh_pixels = np.min(
mesh_pixels_per_image_pixels[indices_of_highest_values]
)

if lowest_mesh_pixels < settings.image_mesh_min_mesh_pixels_per_pixel:
raise exc.InversionException()

return mesh_grid
return mesh_grid

def check_adapt_background_pixels(self, grid, mesh_grid, adapt_data, settings):
"""
Checks the number of mesh pixels in the background of the image-mesh and raises an `InversionException` if
there are fewer mesh pixels in the background than the input settings.
This allows a user to force a model-fit to use image-mesh's which cluster a minimum number of mesh pixels to
the faintest regions of the image data (E.g. the lowest weighted regions). This prevents too few image-mesh
pixels being allocated to the background of the data.
The check works as follows:
1) Find all pixels in the background of the `adapt_data`, which are N pixels with the lowest values, where N is
a percentage given by `settings.image_mesh_adapt_background_percent_check`. If N is 50%, then the half of
pixels in `adapt_data` with the lowest values will be checked.
2) Sum the total number of mesh pixels in these background pixels, thereby estimating the number of mesh pixels
assigned to background pixels.
3) Compare this value to the total number of mesh pixels multiplied
by `settings.image_mesh_adapt_background_percent_threshold` and raise an `InversionException` if the number
of mesh pixels is below this value, meaning the background did not have sufficient mesh pixels in it.
Therefore, by setting `settings.image_mesh_adapt_background_percent_threshold` the code is forced
to adapt the image mesh in a way that places many mesh pixels in the background regions.
Parameters
----------
grid
The masked (y,x) grid of the data coordinates, corresponding to the mask applied to the data. The number of
mesh pixels mapped inside each of this grid's image-pixels is returned.
mesh_grid
The image mesh-grid computed by the class which adapts to the data's mask. The number of image mesh pixels
that fall within each of the data's mask pixels is returned.
adapt_data
A image which represents one or more components in the masked 2D data in the image-plane.
settings
The inversion settings, which have the criteria dictating if the image-mesh has clustered enough or if
an exception is raised.
"""
if settings is not None:
if settings.image_mesh_adapt_background_percent_threshold is not None:
pixels = mesh_grid.shape[0]

pixels_in_background = int(
grid.shape[0] * settings.image_mesh_adapt_background_percent_check
)

indices_of_lowest_values = np.argsort(adapt_data)[:pixels_in_background]
mask_background = np.zeros_like(adapt_data, dtype=bool)
mask_background[indices_of_lowest_values] = True

mesh_pixels_per_image_pixels = self.mesh_pixels_per_image_pixels_from(
grid=grid, mesh_grid=mesh_grid
)

mesh_pixels_in_background = sum(
mesh_pixels_per_image_pixels[mask_background]
)

if mesh_pixels_in_background < (
pixels * settings.image_mesh_adapt_background_percent_threshold
):
raise exc.InversionException()
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,6 @@ def weight_map_from(self, adapt_data: np.ndarray):
return weight_map

def image_plane_mesh_grid_from(
self, grid: Grid2D, adapt_data: Optional[np.ndarray] = None, settings = None
self, grid: Grid2D, adapt_data: Optional[np.ndarray] = None, settings=None
) -> Grid2DIrregular:
raise NotImplementedError
13 changes: 9 additions & 4 deletions autoarray/inversion/pixelization/image_mesh/hilbert.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,10 @@ def __init__(
)

def image_plane_mesh_grid_from(
self, grid: Grid2D, adapt_data: Optional[np.ndarray], settings : SettingsInversion = None
self,
grid: Grid2D,
adapt_data: Optional[np.ndarray],
settings: SettingsInversion = None,
) -> Grid2DIrregular:
"""
Returns an image mesh by running the Hilbert curve on the weight map.
Expand Down Expand Up @@ -300,9 +303,11 @@ def image_plane_mesh_grid_from(
mesh_grid = Grid2DIrregular(values=np.stack((drawn_y, drawn_x), axis=-1))

self.check_mesh_pixels_per_image_pixels(
grid=grid,
mesh_grid=mesh_grid,
settings=settings
grid=grid, mesh_grid=mesh_grid, settings=settings
)

self.check_adapt_background_pixels(
grid=grid, mesh_grid=mesh_grid, adapt_data=adapt_data, settings=settings
)

return mesh_grid
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def __init__(
self.ratio = ratio

def image_plane_mesh_grid_from(
self, grid: Grid2D, adapt_data: Optional[np.ndarray], settings = None
self, grid: Grid2D, adapt_data: Optional[np.ndarray], settings=None
) -> Grid2DIrregular:
"""
Returns an image mesh by running the balanced Hilbert curve on the weight map.
Expand Down
2 changes: 1 addition & 1 deletion autoarray/inversion/pixelization/image_mesh/kmeans.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def __init__(
)

def image_plane_mesh_grid_from(
self, grid: Grid2D, adapt_data: Optional[np.ndarray], settings = None
self, grid: Grid2D, adapt_data: Optional[np.ndarray], settings=None
) -> Grid2DIrregular:
"""
Returns an image mesh by running a KMeans clustering algorithm on the weight map.
Expand Down
2 changes: 1 addition & 1 deletion autoarray/inversion/pixelization/image_mesh/overlay.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def __init__(self, shape=(3, 3)):
self.shape = (int(shape[0]), int(shape[1]))

def image_plane_mesh_grid_from(
self, grid: Grid2D, adapt_data: Optional[np.ndarray] = None, settings = None
self, grid: Grid2D, adapt_data: Optional[np.ndarray] = None, settings=None
) -> Grid2DIrregular:
"""
Returns an image-mesh by overlaying a uniform grid of (y,x) coordinates over the masked image that the
Expand Down
93 changes: 65 additions & 28 deletions test_autoarray/inversion/pixelization/image_mesh/test_abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,38 @@
import autoarray as aa


def test__mesh_pixels_per_image_pixels_from():
@pytest.fixture(name="grid")
def make_grid():
mask = aa.Mask2D.circular(
shape_native=(3, 3),
radius=2.0,
pixel_scales=1.0,
sub_size=1,
)

grid = aa.Grid2D.from_mask(mask=mask)
return aa.Grid2D.from_mask(mask=mask)

mesh_grid = aa.Grid2DIrregular(
values=[(0.0, 0.0), (0.0, 0.0), (0.0, 0.0), (0.0, 1.0), (0.0, 1.0), (-1.0, -1.0)]

@pytest.fixture(name="mesh_grid")
def make_mesh_grid():
return aa.Grid2DIrregular(
values=[
(0.0, 0.0),
(0.0, 0.0),
(0.0, 0.0),
(0.0, 1.0),
(0.0, 1.0),
(-1.0, -1.0),
]
)

image_mesh = aa.image_mesh.Hilbert(pixels=8)

@pytest.fixture(name="image_mesh")
def make_image_mesh():
return aa.image_mesh.Hilbert(pixels=8)


def test__mesh_pixels_per_image_pixels_from(grid, mesh_grid, image_mesh):
mesh_pixels_per_image_pixels = image_mesh.mesh_pixels_per_image_pixels_from(
grid=grid, mesh_grid=mesh_grid
)
Expand All @@ -28,45 +44,66 @@ def test__mesh_pixels_per_image_pixels_from():
np.array([[0, 0, 0], [0, 3, 2], [1, 0, 0]]), 1.0e-4
)

def test__check_mesh_pixels_per_image_pixels():

mask = aa.Mask2D.circular(
shape_native=(3, 3),
radius=2.0,
pixel_scales=1.0,
sub_size=1,
)

grid = aa.Grid2D.from_mask(mask=mask)

mesh_grid = aa.Grid2DIrregular(
values=[(0.0, 0.0), (0.0, 0.0), (0.0, 0.0), (0.0, 1.0), (0.0, 1.0), (-1.0, -1.0)]
)

image_mesh = aa.image_mesh.Hilbert(pixels=8)

def test__check_mesh_pixels_per_image_pixels(grid, mesh_grid, image_mesh):
image_mesh.check_mesh_pixels_per_image_pixels(
grid=grid,
mesh_grid=mesh_grid,
settings=None
grid=grid, mesh_grid=mesh_grid, settings=None
)

image_mesh.check_mesh_pixels_per_image_pixels(
grid=grid,
mesh_grid=mesh_grid,
settings=aa.SettingsInversion(image_mesh_min_mesh_pixels_per_pixel=3, image_mesh_min_mesh_number=1)
settings=aa.SettingsInversion(
image_mesh_min_mesh_pixels_per_pixel=3, image_mesh_min_mesh_number=1
),
)

with pytest.raises(aa.exc.InversionException):
image_mesh.check_mesh_pixels_per_image_pixels(
grid=grid,
mesh_grid=mesh_grid,
settings=aa.SettingsInversion(image_mesh_min_mesh_pixels_per_pixel=5, image_mesh_min_mesh_number=1)
settings=aa.SettingsInversion(
image_mesh_min_mesh_pixels_per_pixel=5, image_mesh_min_mesh_number=1
),
)

with pytest.raises(aa.exc.InversionException):
image_mesh.check_mesh_pixels_per_image_pixels(
grid=grid,
mesh_grid=mesh_grid,
settings=aa.SettingsInversion(image_mesh_min_mesh_pixels_per_pixel=3, image_mesh_min_mesh_number=2)
)
settings=aa.SettingsInversion(
image_mesh_min_mesh_pixels_per_pixel=3, image_mesh_min_mesh_number=2
),
)


def test__check_adapt_background_pixels(grid, mesh_grid, image_mesh):
adapt_data = aa.Array2D.no_mask(
values=[[0.05, 0.05, 0.05], [0.05, 0.6, 0.05], [0.05, 0.05, 0.05]],
pixel_scales=(1.0, 1.0),
)

image_mesh.check_adapt_background_pixels(
grid=grid, mesh_grid=mesh_grid, adapt_data=adapt_data, settings=None
)

image_mesh.check_adapt_background_pixels(
grid=grid,
mesh_grid=mesh_grid,
adapt_data=adapt_data,
settings=aa.SettingsInversion(
image_mesh_adapt_background_percent_threshold=0.05,
image_mesh_adapt_background_percent_check=0.9,
),
)

with pytest.raises(aa.exc.InversionException):
image_mesh.check_adapt_background_pixels(
grid=grid,
mesh_grid=mesh_grid,
adapt_data=adapt_data,
settings=aa.SettingsInversion(
image_mesh_adapt_background_percent_threshold=0.8,
image_mesh_adapt_background_percent_check=0.5,
),
)
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,3 @@ def test__image_plane_mesh_grid_from():
[-1.02590674, -1.70984456],
1.0e-4,
)

0 comments on commit 51d87a0

Please sign in to comment.