Skip to content

Commit a0e65da

Browse files
Jammy2211Jammy2211
authored andcommitted
temporary solution
1 parent fdac408 commit a0e65da

File tree

5 files changed

+176
-123
lines changed

5 files changed

+176
-123
lines changed

autoarray/dataset/grids.py

Lines changed: 17 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from autoarray.inversion.pixelization.border_relocator import BorderRelocator
1010
from autoconf import cached_property
1111

12+
from autoarray import exc
1213

1314
class GridsDataset:
1415
def __init__(
@@ -24,7 +25,7 @@ def __init__(
2425
2526
The following grids are contained:
2627
27-
- `uniform`: A grids of (y,x) coordinates which aligns with the centre of every image pixel of the image data,
28+
- `lp`: A grids of (y,x) coordinates which aligns with the centre of every image pixel of the image data,
2829
which is used for most normal calculations (e.g. evaluating the amount of light that falls in an pixel
2930
from a light profile).
3031
@@ -60,76 +61,33 @@ def __init__(
6061
self.over_sample_size_pixelization = over_sample_size_pixelization
6162
self.psf = psf
6263

63-
@cached_property
64-
def lp(self) -> Union[Grid1D, Grid2D]:
65-
"""
66-
Returns the grid of (y,x) Cartesian coordinates at the centre of every pixel in the masked data, which is used
67-
to perform most normal calculations (e.g. evaluating the amount of light that falls in an pixel from a light
68-
profile).
69-
70-
This grid is computed based on the mask, in particular its pixel-scale and sub-grid size.
71-
72-
Returns
73-
-------
74-
The (y,x) coordinates of every pixel in the data.
75-
"""
76-
return Grid2D.from_mask(
64+
self.lp = Grid2D.from_mask(
7765
mask=self.mask,
7866
over_sample_size=self.over_sample_size_lp,
7967
)
68+
self.lp.over_sampled
8069

81-
@cached_property
82-
def pixelization(self) -> Grid2D:
83-
"""
84-
Returns the grid of (y,x) Cartesian coordinates of every pixel in the masked data which is used
85-
specifically for calculations associated with a pixelization.
86-
87-
The `pixelization` grid is identical to the `uniform` grid but often uses a different over sampling scheme
88-
when performing calculations. For example, the pixelization may benefit from using a a higher `sub_size` than
89-
the `uniform` grid, in order to better prevent aliasing effects.
90-
91-
This grid is computed based on the mask, in particular its pixel-scale and sub-grid size.
92-
93-
Returns
94-
-------
95-
The (y,x) coordinates of every pixel in the data, used for pixelization / inversion calculations.
96-
"""
97-
return Grid2D.from_mask(
70+
self.pixelization = Grid2D.from_mask(
9871
mask=self.mask,
9972
over_sample_size=self.over_sample_size_pixelization,
10073
)
101-
102-
@cached_property
103-
def blurring(self) -> Optional[Grid2D]:
104-
"""
105-
Returns a blurring-grid from a mask and the 2D shape of the PSF kernel.
106-
107-
A blurring grid consists of all pixels that are masked (and therefore have their values set to (0.0, 0.0)),
108-
but are close enough to the unmasked pixels that their values will be convolved into the unmasked those pixels.
109-
This when computing images from light profile objects.
110-
111-
This uses lazy allocation such that the calculation is only performed when the blurring grid is used, ensuring
112-
efficient set up of the `Imaging` class.
113-
114-
Returns
115-
-------
116-
The blurring grid given the mask of the imaging data.
117-
"""
74+
self.pixelization.over_sampled
11875

11976
if self.psf is None:
120-
return None
121-
122-
return self.lp.blurring_grid_via_kernel_shape_from(
123-
kernel_shape_native=self.psf.shape_native,
124-
)
125-
126-
@cached_property
127-
def border_relocator(self) -> BorderRelocator:
128-
return BorderRelocator(
77+
self.blurring = None
78+
else:
79+
try:
80+
self.blurring = self.lp.blurring_grid_via_kernel_shape_from(
81+
kernel_shape_native=self.psf.shape_native,
82+
)
83+
self.blurring.over_sampled
84+
except exc.MaskException:
85+
self.blurring = None
86+
87+
self.border_relocator = BorderRelocator(
12988
mask=self.mask, sub_size=self.over_sample_size_pixelization
13089
)
13190

132-
13391
class GridsInterface:
13492
def __init__(
13593
self,

autoarray/dataset/imaging/dataset.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,7 @@ def __init__(
170170
if psf.mask.shape[0] % 2 == 0 or psf.mask.shape[1] % 2 == 0:
171171
raise exc.KernelException("Kernel2D Kernel2D must be odd")
172172

173-
@cached_property
174-
def grids(self):
175-
return GridsDataset(
173+
self.grids = GridsDataset(
176174
mask=self.data.mask,
177175
over_sample_size_lp=self.over_sample_size_lp,
178176
over_sample_size_pixelization=self.over_sample_size_pixelization,

autoarray/dataset/interferometer/dataset.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,7 @@ def __init__(
101101
else None
102102
)
103103

104-
@cached_property
105-
def grids(self):
106-
return GridsDataset(
104+
self.grids = GridsDataset(
107105
mask=self.real_space_mask,
108106
over_sample_size_lp=self.over_sample_size_lp,
109107
over_sample_size_pixelization=self.over_sample_size_pixelization,

autoarray/operators/over_sampling/over_sample_util.py

Lines changed: 144 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,13 @@ def sub_size_radial_bins_from(
166166

167167
return sub_size_list[bin_indices]
168168

169+
from autoarray.geometry import geometry_util
170+
169171
def grid_2d_slim_over_sampled_via_mask_from(
170-
mask_2d: np.ndarray,
171-
pixel_scales: ty.PixelScales,
172-
sub_size: np.ndarray,
173-
origin: Tuple[float, float] = (0.0, 0.0),
172+
mask_2d: np.ndarray,
173+
pixel_scales: ty.PixelScales,
174+
sub_size: np.ndarray,
175+
origin: Tuple[float, float] = (0.0, 0.0),
174176
) -> np.ndarray:
175177
"""
176178
For a sub-grid, every unmasked pixel of its 2D mask with shape (total_y_pixels, total_x_pixels) is divided into
@@ -211,55 +213,144 @@ def grid_2d_slim_over_sampled_via_mask_from(
211213
grid_slim = grid_2d_slim_over_sampled_via_mask_from(mask=mask, pixel_scales=(0.5, 0.5), sub_size=1, origin=(0.0, 0.0))
212214
"""
213215

214-
H, W = mask_2d.shape
215-
sy, sx = pixel_scales
216-
oy, ox = origin
217-
218-
# 1) Find unmasked pixel indices in row-major order
219-
rows, cols = np.nonzero(~mask_2d)
220-
Npix = rows.size
221-
222-
# 2) Broadcast or validate sub_size array
223-
sub_arr = np.asarray(sub_size)
224-
if sub_arr.ndim == 0:
225-
sub_arr = np.full(Npix, int(sub_arr), int)
226-
elif sub_arr.ndim == 1 and sub_arr.size == Npix:
227-
sub_arr = sub_arr.astype(int)
228-
else:
229-
raise ValueError(f"sub_size must be scalar or length-{Npix} array, got shape {sub_arr.shape}")
230-
231-
# 3) Compute pixel centers (y ↑ up, x → right)
232-
cy = (H - 1) / 2.0
233-
cx = (W - 1) / 2.0
234-
y_pix = (cy - rows) * sy + oy
235-
x_pix = (cols - cx) * sx + ox
236-
237-
# 4) For each pixel, generate its sub-pixel coords and collect
238-
coords_list = []
239-
for i in range(Npix):
240-
s = sub_arr[i]
241-
dy = sy / s
242-
dx = sx / s
243-
244-
# y offsets: from top (+sy/2 - dy/2) down to bottom (-sy/2 + dy/2)
245-
y_off = np.linspace(+sy/2 - dy/2, -sy/2 + dy/2, s)
246-
# x offsets: left to right
247-
x_off = np.linspace(-sx/2 + dx/2, +sx/2 - dx/2, s)
248-
249-
# build subgrid
250-
y_sub, x_sub = np.meshgrid(y_off, x_off, indexing="ij")
251-
y_sub = y_sub.ravel()
252-
x_sub = x_sub.ravel()
253-
254-
# center + offsets
255-
y_center = y_pix[i]
256-
x_center = x_pix[i]
257-
coords = np.stack([y_center + y_sub, x_center + x_sub], axis=1)
258-
259-
coords_list.append(coords)
260-
261-
# 5) Concatenate all sub-pixel blocks in row-major pixel order
262-
return np.vstack(coords_list)
216+
pixels_in_mask = (np.size(mask_2d) - np.sum(mask_2d)).astype(int)
217+
218+
if isinstance(sub_size, int):
219+
sub_size = np.full(
220+
fill_value=sub_size, shape=pixels_in_mask
221+
)
222+
223+
total_sub_pixels = np.sum(sub_size ** 2)
224+
225+
grid_slim = np.zeros(shape=(total_sub_pixels, 2))
226+
227+
centres_scaled = geometry_util.central_scaled_coordinate_2d_from(
228+
shape_native=mask_2d.shape, pixel_scales=pixel_scales, origin=origin
229+
)
230+
231+
index = 0
232+
sub_index = 0
233+
234+
for y in range(mask_2d.shape[0]):
235+
for x in range(mask_2d.shape[1]):
236+
if not mask_2d[y, x]:
237+
sub = sub_size[index]
238+
239+
y_sub_half = pixel_scales[0] / 2
240+
y_sub_step = pixel_scales[0] / (sub)
241+
242+
x_sub_half = pixel_scales[1] / 2
243+
x_sub_step = pixel_scales[1] / (sub)
244+
245+
y_scaled = (y - centres_scaled[0]) * pixel_scales[0]
246+
x_scaled = (x - centres_scaled[1]) * pixel_scales[1]
247+
248+
for y1 in range(sub):
249+
for x1 in range(sub):
250+
grid_slim[sub_index, 0] = -(
251+
y_scaled - y_sub_half + y1 * y_sub_step + (y_sub_step / 2.0)
252+
)
253+
grid_slim[sub_index, 1] = (
254+
x_scaled - x_sub_half + x1 * x_sub_step + (x_sub_step / 2.0)
255+
)
256+
sub_index += 1
257+
258+
index += 1
259+
260+
return grid_slim
261+
262+
263+
#
264+
265+
# def grid_2d_slim_over_sampled_via_mask_from(
266+
# mask_2d: np.ndarray,
267+
# pixel_scales: ty.PixelScales,
268+
# sub_size: np.ndarray,
269+
# origin: Tuple[float, float] = (0.0, 0.0),
270+
# ) -> np.ndarray:
271+
# """
272+
# For a sub-grid, every unmasked pixel of its 2D mask with shape (total_y_pixels, total_x_pixels) is divided into
273+
# a finer uniform grid of shape (total_y_pixels*sub_size, total_x_pixels*sub_size). This routine computes the (y,x)
274+
# scaled coordinates at the centre of every sub-pixel defined by this 2D mask array.
275+
#
276+
# The sub-grid is returned on an array of shape (total_unmasked_pixels*sub_size**2, 2). y coordinates are
277+
# stored in the 0 index of the second dimension, x coordinates in the 1 index. Masked coordinates are therefore
278+
# removed and not included in the slimmed grid.
279+
#
280+
# Grid2D are defined from the top-left corner, where the first unmasked sub-pixel corresponds to index 0.
281+
# Sub-pixels that are part of the same mask array pixel are indexed next to one another, such that the second
282+
# sub-pixel in the first pixel has index 1, its next sub-pixel has index 2, and so forth.
283+
#
284+
# Parameters
285+
# ----------
286+
# mask_2d
287+
# A 2D array of bools, where `False` values are unmasked and therefore included as part of the calculated
288+
# sub-grid.
289+
# pixel_scales
290+
# The (y,x) scaled units to pixel units conversion factor of the 2D mask array.
291+
# sub_size
292+
# The size of the sub-grid that each pixel of the 2D mask array is divided into.
293+
# origin
294+
# The (y,x) origin of the 2D array, which the sub-grid is shifted around.
295+
#
296+
# Returns
297+
# -------
298+
# ndarray
299+
# A slimmed sub grid of (y,x) scaled coordinates at the centre of every pixel unmasked pixel on the 2D mask
300+
# array. The sub grid array has dimensions (total_unmasked_pixels*sub_size**2, 2).
301+
#
302+
# Examples
303+
# --------
304+
# mask = np.array([[True, False, True],
305+
# [False, False, False]
306+
# [True, False, True]])
307+
# grid_slim = grid_2d_slim_over_sampled_via_mask_from(mask=mask, pixel_scales=(0.5, 0.5), sub_size=1, origin=(0.0, 0.0))
308+
# """
309+
#
310+
# H, W = mask_2d.shape
311+
# sy, sx = pixel_scales
312+
# oy, ox = origin
313+
#
314+
# # 1) Find unmasked pixel indices in row-major order
315+
# rows, cols = np.nonzero(~mask_2d)
316+
# Npix = rows.size
317+
#
318+
# # 2) Broadcast or validate sub_size array
319+
# sub_arr = np.asarray(sub_size)
320+
# sub_arr = np.full(Npix, sub_arr, dtype=int) if sub_arr.size == 1 else sub_arr
321+
#
322+
# # 3) Compute pixel centers (y ↑ up, x → right)
323+
# cy = (H - 1) / 2.0
324+
# cx = (W - 1) / 2.0
325+
# y_pix = (cy - rows) * sy + oy
326+
# x_pix = (cols - cx) * sx + ox
327+
#
328+
# # 4) For each pixel, generate its sub-pixel coords and collect
329+
# coords_list = []
330+
# for i in range(Npix):
331+
# s = sub_arr[i]
332+
# dy = sy / s
333+
# dx = sx / s
334+
#
335+
# # y offsets: from top (+sy/2 - dy/2) down to bottom (-sy/2 + dy/2)
336+
# y_off = np.linspace(+sy/2 - dy/2, -sy/2 + dy/2, s)
337+
# # x offsets: left to right
338+
# x_off = np.linspace(-sx/2 + dx/2, +sx/2 - dx/2, s)
339+
#
340+
# # build subgrid
341+
# y_sub, x_sub = np.meshgrid(y_off, x_off, indexing="ij")
342+
# y_sub = y_sub.ravel()
343+
# x_sub = x_sub.ravel()
344+
#
345+
# # center + offsets
346+
# y_center = y_pix[i]
347+
# x_center = x_pix[i]
348+
# coords = np.stack([y_center + y_sub, x_center + x_sub], axis=1)
349+
#
350+
# coords_list.append(coords)
351+
#
352+
# # 5) Concatenate all sub-pixel blocks in row-major pixel order
353+
# return np.vstack(coords_list)
263354

264355

265356
def over_sample_size_via_radial_bins_from(

autoarray/structures/grids/uniform_2d.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -180,18 +180,25 @@ def __init__(
180180

181181
self.over_sampler = OverSampler(sub_size=over_sample_size, mask=mask)
182182

183-
if over_sampled is None:
184-
over_sampled = over_sample_util.grid_2d_slim_over_sampled_via_mask_from(
183+
self._over_sampled = over_sampled
184+
185+
@property
186+
def over_sampled(self):
187+
188+
if self._over_sampled is not None:
189+
return self._over_sampled
190+
191+
over_sampled = over_sample_util.grid_2d_slim_over_sampled_via_mask_from(
185192
mask_2d=np.array(self.mask),
186193
pixel_scales=self.mask.pixel_scales,
187194
sub_size=self.over_sampler.sub_size.array.astype("int"),
188195
origin=self.mask.origin,
189196
)
190197

191-
self.over_sampled = Grid2DIrregular(values=over_sampled)
198+
self._over_sampled = Grid2DIrregular(values=over_sampled)
199+
200+
return self._over_sampled
192201

193-
else:
194-
self.over_sampled = over_sampled
195202

196203
@classmethod
197204
def no_mask(
@@ -696,6 +703,7 @@ def subtracted_from(self, offset: Tuple[(float, float), np.ndarray]) -> "Grid2D"
696703
values=self - np.array(offset),
697704
mask=mask,
698705
over_sample_size=self.over_sample_size,
706+
over_sampled=self.over_sampled - np.array(offset)
699707
)
700708

701709
@property

0 commit comments

Comments
 (0)