@@ -167,190 +167,60 @@ def sub_size_radial_bins_from(
167167 return sub_size_list [bin_indices ]
168168
169169
170- from autoarray .geometry import geometry_util
171-
172-
173170def grid_2d_slim_over_sampled_via_mask_from (
174171 mask_2d : np .ndarray ,
175172 pixel_scales : ty .PixelScales ,
176173 sub_size : np .ndarray ,
177174 origin : Tuple [float , float ] = (0.0 , 0.0 ),
178175) -> np .ndarray :
179176 """
180- For a sub-grid, every unmasked pixel of its 2D mask with shape (total_y_pixels, total_x_pixels) is divided into
181- a finer uniform grid of shape (total_y_pixels*sub_size, total_x_pixels*sub_size). This routine computes the (y,x)
182- scaled coordinates at the centre of every sub-pixel defined by this 2D mask array.
177+ Compute a sub-sampled 2D grid of coordinates for every unmasked pixel,
178+ skipping pixels where sub_size == 0 (to avoid divide-by-zero).
179+ """
183180
184- The sub-grid is returned on an array of shape (total_unmasked_pixels*sub_size**2, 2). y coordinates are
185- stored in the 0 index of the second dimension, x coordinates in the 1 index. Masked coordinates are therefore
186- removed and not included in the slimmed grid.
181+ H , W = mask_2d . shape
182+ sy , sx = pixel_scales
183+ oy , ox = origin
187184
188- Grid2D are defined from the top-left corner, where the first unmasked sub- pixel corresponds to index 0.
189- Sub-pixels that are part of the same mask array pixel are indexed next to one another, such that the second
190- sub-pixel in the first pixel has index 1, its next sub-pixel has index 2, and so forth.
185+ # 1) Find unmasked pixel indices in row-major order
186+ rows , cols = np . nonzero ( ~ mask_2d )
187+ Npix = rows . size
191188
192- Parameters
193- ----------
194- mask_2d
195- A 2D array of bools, where `False` values are unmasked and therefore included as part of the calculated
196- sub-grid.
197- pixel_scales
198- The (y,x) scaled units to pixel units conversion factor of the 2D mask array.
199- sub_size
200- The size of the sub-grid that each pixel of the 2D mask array is divided into.
201- origin
202- The (y,x) origin of the 2D array, which the sub-grid is shifted around.
189+ if Npix == 0 :
190+ return np .empty ((0 , 2 ), dtype = float )
203191
204- Returns
205- -------
206- ndarray
207- A slimmed sub grid of (y,x) scaled coordinates at the centre of every pixel unmasked pixel on the 2D mask
208- array. The sub grid array has dimensions (total_unmasked_pixels*sub_size**2, 2).
192+ # 2) Broadcast or validate sub_size array
193+ sub_arr = np .asarray (sub_size )
194+ sub_arr = np .full (Npix , sub_arr , dtype = int ) if sub_arr .size == 1 else sub_arr
209195
210- Examples
211- --------
212- mask = np.array([[True, False, True],
213- [False, False, False]
214- [True, False, True]])
215- 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))
216- """
196+ # 3) Mask out any pixels with invalid sub_size <= 0
197+ valid_mask = sub_arr > 0
198+ rows , cols , sub_arr = rows [valid_mask ], cols [valid_mask ], sub_arr [valid_mask ]
217199
218- pixels_in_mask = (np .size (mask_2d ) - np .sum (mask_2d )).astype (int )
200+ if sub_arr .size == 0 :
201+ return np .empty ((0 , 2 ), dtype = float )
219202
220- if isinstance (sub_size , int ):
221- sub_size = np .full (fill_value = sub_size , shape = pixels_in_mask )
203+ # 4) Compute pixel centers
204+ cy = (H - 1 ) / 2.0
205+ cx = (W - 1 ) / 2.0
206+ y_pix = (cy - rows ) * sy + oy
207+ x_pix = (cols - cx ) * sx + ox
222208
223- total_sub_pixels = np .sum (sub_size ** 2 )
209+ # 5) For each valid pixel, generate its sub-pixel coords
210+ coords_list = []
211+ for i , s in enumerate (sub_arr ):
212+ dy = sy / s
213+ dx = sx / s
224214
225- grid_slim = np .zeros (shape = (total_sub_pixels , 2 ))
215+ y_off = np .linspace (+ sy / 2 - dy / 2 , - sy / 2 + dy / 2 , s )
216+ x_off = np .linspace (- sx / 2 + dx / 2 , + sx / 2 - dx / 2 , s )
226217
227- centres_scaled = geometry_util .central_scaled_coordinate_2d_from (
228- shape_native = mask_2d .shape , pixel_scales = pixel_scales , origin = origin
229- )
218+ y_sub , x_sub = np .meshgrid (y_off , x_off , indexing = "ij" )
219+
220+ coords = np .stack ([y_pix [i ] + y_sub .ravel (), x_pix [i ] + x_sub .ravel ()], axis = 1 )
221+ coords_list .append (coords )
230222
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)
223+ return np .vstack (coords_list )
354224
355225
356226def over_sample_size_via_radial_bins_from (
0 commit comments