Skip to content

Commit 1395b79

Browse files
Jammy2211Jammy2211
authored andcommitted
WCS stuff implemented
1 parent 2788bcb commit 1395b79

File tree

4 files changed

+343
-3
lines changed

4 files changed

+343
-3
lines changed

autoarray/config/visualize/plots.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ fit: # Settings for plots of all fits (e.g
2121
fit_imaging: {} # Settings for plots of fits to imaging datasets (e.g. FitImagingPlotter).
2222
inversion: # Settings for plots of inversions (e.g. InversionPlotter).
2323
subplot_inversion: true # Plot subplot of all quantities in each inversion (e.g. reconstrucuted image, reconstruction)?
24-
subplot_mappings: true # Plot subplot of the image-to-source pixels mappings of each pixelization?
24+
subplot_mappings: false # Plot subplot of the image-to-source pixels mappings of each pixelization?
2525
data_subtracted: false # Plot individual plots of the data with the other inversion linear objects subtracted?
2626
reconstruction_noise_map: false # Plot image of the noise of every mesh-pixel reconstructed value?
2727
sub_pixels_per_image_pixels: false # Plot the number of sub pixels per masked data pixels?

autoarray/geometry/geometry_2d.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,8 @@ def pixel_coordinates_2d_from(
134134
self, scaled_coordinates_2d: Tuple[float, float]
135135
) -> Tuple[float, float]:
136136
"""
137-
Convert a 2D (y,x) scaled coordinate to a 2D (y,x) pixel coordinate, which are returned as floats such that they
138-
include the decimal offset from each pixel's top-left corner relative to the input scaled coordinate.
137+
Convert a 2D (y,x) scaled coordinate to a 2D (y,x) pixel coordinate, which are returned as integers such that
138+
they do not include the decimal offset from each pixel's top-left corner relative to the input scaled coordinate.
139139
140140
The conversion is performed according to the 2D geometry on a uniform grid, where the pixel coordinate origin
141141
is at the top left corner, such that the pixel [0,0] corresponds to the highest (most positive) y scaled
@@ -190,6 +190,36 @@ def scaled_coordinates_2d_from(
190190
origins=self.origin,
191191
)
192192

193+
def pixel_coordinates_wcs_2d_from(
194+
self, scaled_coordinates_2d: Tuple[float, float]
195+
) -> Tuple[float, float]:
196+
"""
197+
Convert a 2D (y,x) scaled coordinate to a 2D (y,x) pixel coordinate, which are returned as floats such that they
198+
include the decimal offset from each pixel's top-left corner relative to the input scaled coordinate.
199+
200+
The conversion is performed according to the 2D geometry on a uniform grid, where the pixel coordinate origin
201+
is at the top left corner, such that the pixel [0,0] corresponds to the highest (most positive) y scaled
202+
coordinate and lowest (most negative) x scaled coordinate on the gird.
203+
204+
The scaled coordinate is defined by an origin and coordinates are shifted to this origin before computing their
205+
1D grid pixel coordinate values.
206+
207+
Parameters
208+
----------
209+
scaled_coordinates_2d
210+
The 2D (y,x) coordinates in scaled units which are converted to pixel coordinates.
211+
212+
Returns
213+
-------
214+
A 2D (y,x) pixel-value coordinate.
215+
"""
216+
return geometry_util.pixel_coordinates_wcs_2d_from(
217+
scaled_coordinates_2d=scaled_coordinates_2d,
218+
shape_native=self.shape_native,
219+
pixel_scales=self.pixel_scales,
220+
origins=self.origin,
221+
)
222+
193223
def scaled_coordinate_2d_to_scaled_at_pixel_centre_from(
194224
self, scaled_coordinate_2d: Tuple[float, float]
195225
) -> Tuple[float, float]:

autoarray/geometry/geometry_util.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,41 @@ def scaled_coordinates_2d_from(
357357
return (y_pixel, x_pixel)
358358

359359

360+
def pixel_coordinates_wcs_2d_from(
361+
scaled_coordinates_2d,
362+
shape_native,
363+
pixel_scales,
364+
origins=(0.0, 0.0),
365+
):
366+
"""
367+
Return FITS / WCS pixel coordinates (1-based, pixel-centre convention) as floats.
368+
369+
This function returns continuous pixel coordinates suitable for Astropy WCS
370+
transforms (e.g. wcs_pix2world with origin=1). Pixel centres lie at integer
371+
values; for an image of shape (ny, nx) the geometric centre is:
372+
((ny + 1)/2, (nx + 1)/2)
373+
e.g. (100, 100) -> (50.5, 50.5).
374+
"""
375+
ny, nx = shape_native
376+
377+
# Geometric centre in WCS pixel coordinates (1-based, pixel centres at integers)
378+
ycen_wcs = (ny + 1) / 2.0
379+
xcen_wcs = (nx + 1) / 2.0
380+
381+
# Continuous WCS pixel coordinates (NO int-cast, NO +0.5 binning)
382+
y_wcs = (
383+
(-scaled_coordinates_2d[0] + origins[0]) / pixel_scales[0]
384+
+ ycen_wcs
385+
)
386+
x_wcs = (
387+
(scaled_coordinates_2d[1] - origins[1]) / pixel_scales[1]
388+
+ xcen_wcs
389+
)
390+
391+
return (y_wcs, x_wcs)
392+
393+
394+
360395
def transform_grid_2d_to_reference_frame(
361396
grid_2d: np.ndarray, centre: Tuple[float, float], angle: float, xp=np
362397
) -> np.ndarray:

test_autoarray/geometry/test_geometry_util.py

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -972,6 +972,281 @@ def test__pixel_coordinates_2d_from():
972972
assert scaled_coordinates == (0.0, 6.0)
973973

974974

975+
import pytest
976+
977+
def test__pixel_coordinates_wcs_2d_from():
978+
# -----------------------------
979+
# (2,2) grid: centre is (1.5, 1.5) in WCS pixels
980+
# pixel_scales = (2,2)
981+
# -----------------------------
982+
983+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
984+
scaled_coordinates_2d=(1.0, -1.0),
985+
shape_native=(2, 2),
986+
pixel_scales=(2.0, 2.0),
987+
)
988+
assert pixel_coordinates == pytest.approx((1.0, 1.0))
989+
990+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
991+
scaled_coordinates_2d=(1.0, 1.0),
992+
shape_native=(2, 2),
993+
pixel_scales=(2.0, 2.0),
994+
)
995+
assert pixel_coordinates == pytest.approx((1.0, 2.0))
996+
997+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
998+
scaled_coordinates_2d=(-1.0, -1.0),
999+
shape_native=(2, 2),
1000+
pixel_scales=(2.0, 2.0),
1001+
)
1002+
assert pixel_coordinates == pytest.approx((2.0, 1.0))
1003+
1004+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1005+
scaled_coordinates_2d=(-1.0, 1.0),
1006+
shape_native=(2, 2),
1007+
pixel_scales=(2.0, 2.0),
1008+
)
1009+
assert pixel_coordinates == pytest.approx((2.0, 2.0))
1010+
1011+
# -----------------------------
1012+
# (3,3) grid: centre is (2.0, 2.0) in WCS pixels
1013+
# pixel_scales = (3,3)
1014+
# -----------------------------
1015+
1016+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1017+
scaled_coordinates_2d=(3.0, -3.0),
1018+
shape_native=(3, 3),
1019+
pixel_scales=(3.0, 3.0),
1020+
)
1021+
assert pixel_coordinates == pytest.approx((1.0, 1.0))
1022+
1023+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1024+
scaled_coordinates_2d=(3.0, 0.0),
1025+
shape_native=(3, 3),
1026+
pixel_scales=(3.0, 3.0),
1027+
)
1028+
assert pixel_coordinates == pytest.approx((1.0, 2.0))
1029+
1030+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1031+
scaled_coordinates_2d=(3.0, 3.0),
1032+
shape_native=(3, 3),
1033+
pixel_scales=(3.0, 3.0),
1034+
)
1035+
assert pixel_coordinates == pytest.approx((1.0, 3.0))
1036+
1037+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1038+
scaled_coordinates_2d=(0.0, -3.0),
1039+
shape_native=(3, 3),
1040+
pixel_scales=(3.0, 3.0),
1041+
)
1042+
assert pixel_coordinates == pytest.approx((2.0, 1.0))
1043+
1044+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1045+
scaled_coordinates_2d=(0.0, 0.0),
1046+
shape_native=(3, 3),
1047+
pixel_scales=(3.0, 3.0),
1048+
)
1049+
assert pixel_coordinates == pytest.approx((2.0, 2.0))
1050+
1051+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1052+
scaled_coordinates_2d=(0.0, 3.0),
1053+
shape_native=(3, 3),
1054+
pixel_scales=(3.0, 3.0),
1055+
)
1056+
assert pixel_coordinates == pytest.approx((2.0, 3.0))
1057+
1058+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1059+
scaled_coordinates_2d=(-3.0, -3.0),
1060+
shape_native=(3, 3),
1061+
pixel_scales=(3.0, 3.0),
1062+
)
1063+
assert pixel_coordinates == pytest.approx((3.0, 1.0))
1064+
1065+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1066+
scaled_coordinates_2d=(-3.0, 0.0),
1067+
shape_native=(3, 3),
1068+
pixel_scales=(3.0, 3.0),
1069+
)
1070+
assert pixel_coordinates == pytest.approx((3.0, 2.0))
1071+
1072+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1073+
scaled_coordinates_2d=(-3.0, 3.0),
1074+
shape_native=(3, 3),
1075+
pixel_scales=(3.0, 3.0),
1076+
)
1077+
assert pixel_coordinates == pytest.approx((3.0, 3.0))
1078+
1079+
# -----------------------------------------
1080+
# Inputs near corners (continuous coordinates)
1081+
# -----------------------------------------
1082+
1083+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1084+
scaled_coordinates_2d=(1.99, -1.99),
1085+
shape_native=(2, 2),
1086+
pixel_scales=(2.0, 2.0),
1087+
)
1088+
assert pixel_coordinates == pytest.approx((0.505, 0.505))
1089+
1090+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1091+
scaled_coordinates_2d=(1.99, -0.01),
1092+
shape_native=(2, 2),
1093+
pixel_scales=(2.0, 2.0),
1094+
)
1095+
assert pixel_coordinates == pytest.approx((0.505, 1.495))
1096+
1097+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1098+
scaled_coordinates_2d=(0.01, -1.99),
1099+
shape_native=(2, 2),
1100+
pixel_scales=(2.0, 2.0),
1101+
)
1102+
assert pixel_coordinates == pytest.approx((1.495, 0.505))
1103+
1104+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1105+
scaled_coordinates_2d=(0.01, -0.01),
1106+
shape_native=(2, 2),
1107+
pixel_scales=(2.0, 2.0),
1108+
)
1109+
assert pixel_coordinates == pytest.approx((1.495, 1.495))
1110+
1111+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1112+
scaled_coordinates_2d=(2.01, 0.01),
1113+
shape_native=(2, 2),
1114+
pixel_scales=(2.0, 2.0),
1115+
)
1116+
assert pixel_coordinates == pytest.approx((0.495, 1.505))
1117+
1118+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1119+
scaled_coordinates_2d=(2.01, 1.99),
1120+
shape_native=(2, 2),
1121+
pixel_scales=(2.0, 2.0),
1122+
)
1123+
assert pixel_coordinates == pytest.approx((0.495, 2.495))
1124+
1125+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1126+
scaled_coordinates_2d=(0.01, 0.01),
1127+
shape_native=(2, 2),
1128+
pixel_scales=(2.0, 2.0),
1129+
)
1130+
assert pixel_coordinates == pytest.approx((1.495, 1.505))
1131+
1132+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1133+
scaled_coordinates_2d=(0.01, 1.99),
1134+
shape_native=(2, 2),
1135+
pixel_scales=(2.0, 2.0),
1136+
)
1137+
assert pixel_coordinates == pytest.approx((1.495, 2.495))
1138+
1139+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1140+
scaled_coordinates_2d=(-0.01, -1.99),
1141+
shape_native=(2, 2),
1142+
pixel_scales=(2.0, 2.0),
1143+
)
1144+
assert pixel_coordinates == pytest.approx((1.505, 0.505))
1145+
1146+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1147+
scaled_coordinates_2d=(-0.01, -0.01),
1148+
shape_native=(2, 2),
1149+
pixel_scales=(2.0, 2.0),
1150+
)
1151+
assert pixel_coordinates == pytest.approx((1.505, 1.495))
1152+
1153+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1154+
scaled_coordinates_2d=(-1.99, -1.99),
1155+
shape_native=(2, 2),
1156+
pixel_scales=(2.0, 2.0),
1157+
)
1158+
assert pixel_coordinates == pytest.approx((2.495, 0.505))
1159+
1160+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1161+
scaled_coordinates_2d=(-1.99, -0.01),
1162+
shape_native=(2, 2),
1163+
pixel_scales=(2.0, 2.0),
1164+
)
1165+
assert pixel_coordinates == pytest.approx((2.495, 1.495))
1166+
1167+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1168+
scaled_coordinates_2d=(-0.01, 0.01),
1169+
shape_native=(2, 2),
1170+
pixel_scales=(2.0, 2.0),
1171+
)
1172+
assert pixel_coordinates == pytest.approx((1.505, 1.505))
1173+
1174+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1175+
scaled_coordinates_2d=(-0.01, 1.99),
1176+
shape_native=(2, 2),
1177+
pixel_scales=(2.0, 2.0),
1178+
)
1179+
assert pixel_coordinates == pytest.approx((1.505, 2.495))
1180+
1181+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1182+
scaled_coordinates_2d=(-1.99, 0.01),
1183+
shape_native=(2, 2),
1184+
pixel_scales=(2.0, 2.0),
1185+
)
1186+
assert pixel_coordinates == pytest.approx((2.495, 1.505))
1187+
1188+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1189+
scaled_coordinates_2d=(-1.99, 1.99),
1190+
shape_native=(2, 2),
1191+
pixel_scales=(2.0, 2.0),
1192+
)
1193+
assert pixel_coordinates == pytest.approx((2.495, 2.495))
1194+
1195+
# -----------------------------------------
1196+
# Inputs are centres (origins shift), still continuous outputs
1197+
# -----------------------------------------
1198+
1199+
# Inputs are centres (origins shift), continuous outputs
1200+
1201+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1202+
scaled_coordinates_2d=(2.0, 0.0),
1203+
shape_native=(2, 2),
1204+
pixel_scales=(2.0, 2.0),
1205+
origins=(1.0, 1.0),
1206+
)
1207+
assert pixel_coordinates == pytest.approx((1.0, 1.0))
1208+
1209+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1210+
scaled_coordinates_2d=(2.0, 2.0),
1211+
shape_native=(2, 2),
1212+
pixel_scales=(2.0, 2.0),
1213+
origins=(1.0, 1.0),
1214+
)
1215+
assert pixel_coordinates == pytest.approx((1.0, 2.0))
1216+
1217+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1218+
scaled_coordinates_2d=(0.0, 0.0),
1219+
shape_native=(2, 2),
1220+
pixel_scales=(2.0, 2.0),
1221+
origins=(1.0, 1.0),
1222+
)
1223+
assert pixel_coordinates == pytest.approx((2.0, 1.0))
1224+
1225+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1226+
scaled_coordinates_2d=(0.0, 2.0),
1227+
shape_native=(2, 2),
1228+
pixel_scales=(2.0, 2.0),
1229+
origins=(1.0, 1.0),
1230+
)
1231+
assert pixel_coordinates == pytest.approx((2.0, 2.0))
1232+
1233+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1234+
scaled_coordinates_2d=(6.0, 0.0),
1235+
shape_native=(3, 3),
1236+
pixel_scales=(3.0, 3.0),
1237+
origins=(3.0, 3.0),
1238+
)
1239+
assert pixel_coordinates == pytest.approx((1.0, 1.0))
1240+
1241+
pixel_coordinates = aa.util.geometry.pixel_coordinates_wcs_2d_from(
1242+
scaled_coordinates_2d=(6.0, 3.0),
1243+
shape_native=(4, 4),
1244+
pixel_scales=(3.0, 3.0),
1245+
origins=(3.0, 3.0),
1246+
)
1247+
assert pixel_coordinates == pytest.approx((1.5, 2.5))
1248+
1249+
9751250
def test__transform_2d_grid_to_reference_frame():
9761251
grid_2d = np.array([[0.0, 1.0], [1.0, 1.0], [1.0, 0.0]])
9771252

0 commit comments

Comments
 (0)