Skip to content

Commit

Permalink
implementation seems to work but buggy
Browse files Browse the repository at this point in the history
  • Loading branch information
Jammy2211 committed Nov 25, 2024
1 parent d850b27 commit 7322ad6
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 86 deletions.
26 changes: 13 additions & 13 deletions autogalaxy/ellipse/ellipse/ellipse.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def total_points_from(self, pixel_scale: float) -> int:

return np.min([500, int(np.round(circular_radius_pixels, 1))])

def angles_from_x0_from(self, pixel_scale: float, mask_interp = None) -> np.ndarray:
def angles_from_x0_from(self, pixel_scale: float, n_i : int = 0) -> np.ndarray:
"""
Returns the angles from the x-axis to a discrete number of points ranging from 0.0 to 2.0 * np.pi radians.
Expand Down Expand Up @@ -136,9 +136,9 @@ def angles_from_x0_from(self, pixel_scale: float, mask_interp = None) -> np.ndar
"""
total_points = self.total_points_from(pixel_scale)

return np.linspace(0.0, 2.0 * np.pi, total_points)[:-1]
return np.linspace(0.0, 2.0 * np.pi, total_points + n_i)[:-1]

def ellipse_radii_from_major_axis_from(self, pixel_scale: float, mask_interp = None) -> np.ndarray:
def ellipse_radii_from_major_axis_from(self, pixel_scale: float, n_i : int = 0) -> np.ndarray:
"""
Returns the distance from the centre of the ellipse to every point on the ellipse, which are called
the ellipse radii.
Expand All @@ -159,7 +159,7 @@ def ellipse_radii_from_major_axis_from(self, pixel_scale: float, mask_interp = N
The ellipse radii from the major-axis of the ellipse.
"""

angles_from_x0 = self.angles_from_x0_from(pixel_scale=pixel_scale, mask_interp=mask_interp)
angles_from_x0 = self.angles_from_x0_from(pixel_scale=pixel_scale, n_i=n_i)

return np.divide(
self.major_axis * self.minor_axis,
Expand All @@ -173,7 +173,7 @@ def ellipse_radii_from_major_axis_from(self, pixel_scale: float, mask_interp = N
),
)

def x_from_major_axis_from(self, pixel_scale: float, mask_interp = None) -> np.ndarray:
def x_from_major_axis_from(self, pixel_scale: float, n_i : int = 0) -> np.ndarray:
"""
Returns the x-coordinates of the points on the ellipse, starting from the x-coordinate of the major-axis
of the ellipse after rotation by its `angle` and moving counter-clockwise.
Expand All @@ -191,15 +191,15 @@ def x_from_major_axis_from(self, pixel_scale: float, mask_interp = None) -> np.n
The x-coordinates of the points on the ellipse.
"""

angles_from_x0 = self.angles_from_x0_from(pixel_scale=pixel_scale, mask_interp=mask_interp)
angles_from_x0 = self.angles_from_x0_from(pixel_scale=pixel_scale, n_i=n_i)
ellipse_radii_from_major_axis = self.ellipse_radii_from_major_axis_from(
pixel_scale=pixel_scale, mask_interp=mask_interp
pixel_scale=pixel_scale, n_i=n_i
)

return ellipse_radii_from_major_axis * np.cos(angles_from_x0) + self.centre[1]

def y_from_major_axis_from(
self, pixel_scale: float, flip_y: bool = False, mask_interp = None
self, pixel_scale: float, flip_y: bool = False, n_i : int = 0
) -> np.ndarray:
"""
Returns the y-coordinates of the points on the ellipse, starting from the y-coordinate of the major-axis
Expand All @@ -226,9 +226,9 @@ def y_from_major_axis_from(
-------
The y-coordinates of the points on the ellipse.
"""
angles_from_x0 = self.angles_from_x0_from(pixel_scale=pixel_scale, mask_interp=mask_interp)
angles_from_x0 = self.angles_from_x0_from(pixel_scale=pixel_scale, n_i=n_i)
ellipse_radii_from_major_axis = self.ellipse_radii_from_major_axis_from(
pixel_scale=pixel_scale, mask_interp=mask_interp
pixel_scale=pixel_scale, n_i=n_i
)

if flip_y:
Expand All @@ -242,7 +242,7 @@ def y_from_major_axis_from(
)

def points_from_major_axis_from(
self, pixel_scale: float, flip_y: bool = False, mask_interp = None
self, pixel_scale: float, flip_y: bool = False, n_i : int = 0
) -> np.ndarray:
"""
Returns the (y,x) coordinates of the points on the ellipse, starting from the major-axis of the ellipse
Expand All @@ -267,8 +267,8 @@ def points_from_major_axis_from(
The (y,x) coordinates of the points on the ellipse.
"""

x = self.x_from_major_axis_from(pixel_scale=pixel_scale, mask_interp=mask_interp)
y = self.y_from_major_axis_from(pixel_scale=pixel_scale, flip_y=flip_y, mask_interp=mask_interp)
x = self.x_from_major_axis_from(pixel_scale=pixel_scale, n_i=n_i)
y = self.y_from_major_axis_from(pixel_scale=pixel_scale, flip_y=flip_y, n_i=n_i)

idx = np.logical_or(np.isnan(x), np.isnan(y))
if np.sum(idx) > 0.0:
Expand Down
127 changes: 79 additions & 48 deletions autogalaxy/ellipse/fit_ellipse.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,40 @@ def points_from_major_axis_from(self, flip_y: bool = False) -> np.ndarray:
The (y,x) coordinates on the ellipse where the interpolation occurs.
"""
points = self.ellipse.points_from_major_axis_from(
pixel_scale=self.dataset.pixel_scales[0], flip_y=flip_y
pixel_scale=self.dataset.pixel_scales[0], flip_y=flip_y,
)

if self.interp.mask_interp is not None:

i_total = 300

total_points_required = points.shape[0]

for i in range(1, i_total + 1):

total_points = points.shape[0]
total_points_masked = np.sum(self.interp.mask_interp(points) > 0)

if total_points_required == total_points - total_points_masked:
continue

points = self.ellipse.points_from_major_axis_from(pixel_scale=self.dataset.pixel_scales[0],
flip_y=flip_y, n_i=i)

if i == i_total:

raise ValueError(
"""
The code has attempted to add over 1000 points to the ellipse and still not found a set of points that
do not hit the mask with the expected number of points.
This is likely due to the mask being too large or a strange geometry, and the code is unable to find a
set of points that do not hit the mask.
"""
)

points = points[self.interp.mask_interp(points) == 0]

if self.multipole_list is not None:
for multipole in self.multipole_list:
points = multipole.points_perturbed_from(
Expand All @@ -80,51 +111,51 @@ def _points_from_major_axis(self) -> np.ndarray:
"""
return self.points_from_major_axis_from()

@property
def mask_interp(self) -> np.ndarray:
"""
Returns the mask values of the dataset that the ellipse fits, which are computed by overlaying the ellipse over
the 2D data and performing a 2D interpolation at discrete (y,x) coordinates on the ellipse on the dataset's
mask.
When an input (y,x) coordinate intepolates only unmasked values (`data.mask=False`) the intepolatred value
is 0.0, where if it interpolates one or a masked value (`data.mask=True`), the interpolated value is positive.
To mask all values which interpolate a masked value, all interpolated values above 1 and converted to `True`.
This mask is used to remove these pixels from a fit and evaluate how many ellipse points are used for each
ellipse fit.
The (y,x) coordinates on the ellipse where the interpolation occurs are computed in the
`points_from_major_axis` property of the `Ellipse` class, with the documentation describing how these points
are computed.
Returns
-------
The data values of the ellipse fits, computed via a 2D interpolation of where the ellipse
overlaps the data.
"""
return self.interp.mask_interp(self._points_from_major_axis) > 0.0

@property
def total_points_interp(self) -> int:
"""
Returns the total number of points used to interpolate the data and noise-map values of the ellipse.
For example, if the ellipse spans 10 pixels, the total number of points will be 10.
The calculation removes points if one or more interpolated value uses a masked value, meaning the interpolation
is not reliable.
The (y,x) coordinates on the ellipse where the interpolation occurs are computed in the
`points_from_major_axis` property of the `Ellipse` class, with the documentation describing how these points
are computed.
Returns
-------
The noise-map values of the ellipse fits, computed via a 2D interpolation of where the ellipse
overlaps the noise-map.
"""
return self.data_interp[np.invert(self.mask_interp)].shape[0]
# @property
# def mask_interp(self) -> np.ndarray:
# """
# Returns the mask values of the dataset that the ellipse fits, which are computed by overlaying the ellipse over
# the 2D data and performing a 2D interpolation at discrete (y,x) coordinates on the ellipse on the dataset's
# mask.
#
# When an input (y,x) coordinate intepolates only unmasked values (`data.mask=False`) the intepolatred value
# is 0.0, where if it interpolates one or a masked value (`data.mask=True`), the interpolated value is positive.
# To mask all values which interpolate a masked value, all interpolated values above 1 and converted to `True`.
#
# This mask is used to remove these pixels from a fit and evaluate how many ellipse points are used for each
# ellipse fit.
#
# The (y,x) coordinates on the ellipse where the interpolation occurs are computed in the
# `points_from_major_axis` property of the `Ellipse` class, with the documentation describing how these points
# are computed.
#
# Returns
# -------
# The data values of the ellipse fits, computed via a 2D interpolation of where the ellipse
# overlaps the data.
# """
# return self.interp.mask_interp(self._points_from_major_axis) > 0.0

# @property
# def total_points_interp(self) -> int:
# """
# Returns the total number of points used to interpolate the data and noise-map values of the ellipse.
#
# For example, if the ellipse spans 10 pixels, the total number of points will be 10.
#
# The calculation removes points if one or more interpolated value uses a masked value, meaning the interpolation
# is not reliable.
#
# The (y,x) coordinates on the ellipse where the interpolation occurs are computed in the
# `points_from_major_axis` property of the `Ellipse` class, with the documentation describing how these points
# are computed.
#
# Returns
# -------
# The noise-map values of the ellipse fits, computed via a 2D interpolation of where the ellipse
# overlaps the noise-map.
# """
# return self.data_interp[np.invert(self.mask_interp)].shape[0]

@property
def data_interp(self) -> aa.ArrayIrregular:
Expand All @@ -146,7 +177,7 @@ def data_interp(self) -> aa.ArrayIrregular:
"""
data = self.interp.data_interp(self._points_from_major_axis)

data[self.mask_interp] = np.nan
# data[self.mask_interp] = np.nan

return aa.ArrayIrregular(values=data)

Expand All @@ -170,7 +201,7 @@ def noise_map_interp(self) -> aa.ArrayIrregular:
"""
noise_map = self.interp.noise_map_interp(self._points_from_major_axis)

noise_map[self.mask_interp] = np.nan
# noise_map[self.mask_interp] = np.nan

return aa.ArrayIrregular(values=noise_map)

Expand Down
10 changes: 7 additions & 3 deletions autogalaxy/ellipse/model/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from autogalaxy.ellipse.model.result import ResultEllipse
from autogalaxy.ellipse.model.visualizer import VisualizerEllipse

from autogalaxy import exc

logger = logging.getLogger(__name__)

logger.setLevel(level="INFO")
Expand Down Expand Up @@ -73,9 +75,11 @@ def log_likelihood_function(self, instance: af.ModelInstance) -> float:
float
The log likelihood indicating how well this model instance fitted the imaging data.
"""
fit_list = self.fit_list_from(instance=instance)

return sum(fit.log_likelihood for fit in fit_list)
try:
fit_list = self.fit_list_from(instance=instance)
return sum(fit.log_likelihood for fit in fit_list)
except ValueError as e:
raise exc.FitException from e

def fit_list_from(self, instance: af.ModelInstance) -> List[FitEllipse]:
"""
Expand Down
6 changes: 5 additions & 1 deletion autogalaxy/ellipse/model/plotter_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,11 @@ def should_plot(name):
fit_list=fit_list, mat_plot_2d=mat_plot_2d, include_2d=self.include_2d
)

fit_plotter.figures_2d(data=should_plot("data"))
try:
fit_plotter.figures_2d(data=should_plot("data"))
except ValueError:
print(fit_plotter.fit_list[0].ellipse.major_axis)
print(fit_plotter.fit_list[0].ellipse.ell_comps)

if should_plot("data_no_ellipse"):
fit_plotter.figures_2d(
Expand Down
20 changes: 1 addition & 19 deletions test_autogalaxy/ellipse/ellipse/test_ellipse.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,25 +82,6 @@ def test__angles_from_x0():
)


def test__angles_from_x0__with_n_i():
ellipse = ag.Ellipse(centre=(0.0, 0.0), ell_comps=(0.0, 0.0), major_axis=1.0)

angles = ellipse.angles_from_x0_from(pixel_scale=1.0, n_i=0)

print(angles)

angles = ellipse.angles_from_x0_from(pixel_scale=1.0, n_i=1)

print(angles)

jghjhjhg

assert ellipse.angles_from_x0_from(pixel_scale=1.0)[0] == pytest.approx(0.0, 1.0e-4)
assert ellipse.angles_from_x0_from(pixel_scale=1.0)[1] == pytest.approx(
1.25663706143, 1.0e-4
)


def test__x_from_major_axis():
ellipse = ag.Ellipse(centre=(0.0, 0.0), ell_comps=(0.0, 0.0), major_axis=1.0)

Expand Down Expand Up @@ -171,3 +152,4 @@ def test__points_from_major_axis():
assert ellipse.points_from_major_axis_from(pixel_scale=1.0)[1][0] == pytest.approx(
-0.2123224755, 1.0e-4
)

Loading

0 comments on commit 7322ad6

Please sign in to comment.