Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 24 additions & 14 deletions autoarray/plot/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1205,8 +1205,10 @@ def plot_regions(
region_alpha
The alpha of each region's fill; the outline is always drawn opaque.
region_labels
A text label drawn at the centre of each region, e.g. ``["1", "2"]``. ``None`` draws no
labels.
A text label drawn at the centre of each polygon of each region, e.g. ``["1", "2"]``.
A region with several polygons -- the multiple images of one lensed source -- gets its
label repeated once per image, so every image is labelled and no label lands on the
empty sky between them. ``None`` draws no labels.
"""
if regions is None:
return
Expand Down Expand Up @@ -1240,15 +1242,23 @@ def plot_regions(
ax.plot(polygon[:, 1], polygon[:, 0], color=color, linewidth=1, zorder=5)

if region_labels is not None and i < len(region_labels) and len(points) > 0:
stacked = np.concatenate(points, axis=0)

ax.annotate(
str(region_labels[i]),
xy=(float(np.mean(stacked[:, 1])), float(np.mean(stacked[:, 0]))),
color=color,
fontsize=12,
fontweight="bold",
ha="center",
va="center",
zorder=6,
)
# One label per polygon, at that polygon's own centre -- not one label at the
# centre of all of them. A region whose polygons are the multiple images of a
# lensed source has its polygons on opposite sides of the lens, so a single
# label at their combined mean lands between them, on empty sky, labelling
# nothing. A single-polygon region is unaffected: its polygon's mean is the
# region's mean.
for polygon in points:
ax.annotate(
str(region_labels[i]),
xy=(
float(np.mean(polygon[:, 1])),
float(np.mean(polygon[:, 0])),
),
color=color,
fontsize=12,
fontweight="bold",
ha="center",
va="center",
zorder=6,
)
1 change: 0 additions & 1 deletion autoarray/structures/triangles/array_np.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,6 @@ def with_vertices(self, vertices: np.ndarray) -> "ArrayTrianglesNp":
-------
The new set of triangles with the new vertices.
"""
bbbb
return ArrayTrianglesNp(
indices=self.indices,
vertices=vertices,
Expand Down
37 changes: 30 additions & 7 deletions autoarray/structures/triangles/coordinate_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,23 +46,46 @@ def __init__(
@classmethod
def for_limits_and_scale(
cls,
x_min: float,
x_max: float,
y_min: float,
y_max: float,
x_min: float,
x_max: float,
scale: float = 1.0,
**_,
):
"""
Tile the rectangle ``[y_min, y_max] x [x_min, x_max]`` with equilateral triangles.

Element ``0`` of every vertex spans the ``y`` limits and element ``1`` the ``x``
limits, matching `ArrayTrianglesNp.for_limits_and_scale`, the ``(y, x)`` order of
every PyAuto grid, and the ``element 0 <-> element 0`` convention `Shape.contains`
and `Shape.mask` are documented with.

The two axes were previously the other way round while the signature named its
first pair ``x_min``/``x_max``, so both the keyword callers (`AbstractSolver`) and
the positional caller (`AbstractTriangles.for_grid`) tiled the *transposed*
rectangle. On a square grid that is invisible; on a rectangular one the solver
searched a box the data does not occupy and silently missed multiple images which
lay inside the grid (a 24x80 grid of 0.05" pixels found one of an Isothermal's two
images instead of both).

Parameters
----------
y_min, y_max, x_min, x_max
The limits of the rectangle to tile.
scale
The side length of the triangles.
"""
import jax.numpy as jnp

x_shift = int(2 * x_min / scale)
y_shift = int(y_min / (HEIGHT_FACTOR * scale))
y_shift = int(2 * y_min / scale)
x_shift = int(x_min / (HEIGHT_FACTOR * scale))

coordinates = []

for x in range(x_shift, int(2 * x_max / scale) + 1):
for y in range(y_shift - 1, int(y_max / (HEIGHT_FACTOR * scale)) + 2):
coordinates.append([x, y])
for y in range(y_shift, int(2 * y_max / scale) + 1):
for x in range(x_shift - 1, int(x_max / (HEIGHT_FACTOR * scale)) + 2):
coordinates.append([y, x])

return cls(
coordinates=jnp.array(coordinates),
Expand Down
37 changes: 30 additions & 7 deletions autoarray/structures/triangles/coordinate_array_np.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,21 +92,44 @@ def flip_array(self) -> np.ndarray:
@classmethod
def for_limits_and_scale(
cls,
x_min: float,
x_max: float,
y_min: float,
y_max: float,
x_min: float,
x_max: float,
scale: float = 1.0,
**_,
):
x_shift = int(2 * x_min / scale)
y_shift = int(y_min / (HEIGHT_FACTOR * scale))
"""
Tile the rectangle ``[y_min, y_max] x [x_min, x_max]`` with equilateral triangles.

Element ``0`` of every vertex spans the ``y`` limits and element ``1`` the ``x``
limits, matching `ArrayTrianglesNp.for_limits_and_scale`, the ``(y, x)`` order of
every PyAuto grid, and the ``element 0 <-> element 0`` convention `Shape.contains`
and `Shape.mask` are documented with.

The two axes were previously the other way round while the signature named its
first pair ``x_min``/``x_max``, so both the keyword callers (`AbstractSolver`) and
the positional caller (`AbstractTriangles.for_grid`) tiled the *transposed*
rectangle. On a square grid that is invisible; on a rectangular one the solver
searched a box the data does not occupy and silently missed multiple images which
lay inside the grid (a 24x80 grid of 0.05" pixels found one of an Isothermal's two
images instead of both).

Parameters
----------
y_min, y_max, x_min, x_max
The limits of the rectangle to tile.
scale
The side length of the triangles.
"""
y_shift = int(2 * y_min / scale)
x_shift = int(x_min / (HEIGHT_FACTOR * scale))

coordinates = []

for x in range(x_shift, int(2 * x_max / scale) + 1):
for y in range(y_shift - 1, int(y_max / (HEIGHT_FACTOR * scale)) + 2):
coordinates.append([x, y])
for y in range(y_shift, int(2 * y_max / scale) + 1):
for x in range(x_shift - 1, int(x_max / (HEIGHT_FACTOR * scale)) + 2):
coordinates.append([y, x])

return CoordinateArrayTrianglesNp(
coordinates=np.array(coordinates, dtype=np.int32),
Expand Down
Loading
Loading