@@ -34,12 +34,19 @@ def mask_2d_centres_from(
3434
3535 Examples
3636 --------
37- centres_scaled = mask_2d_centres_from(shape_native=(5, 5), pixel_scales=(0.5, 0.5), centre=(0.0, 0.0))
37+ >>> centres_scaled = mask_2d_centres_from(shape_native=(5, 5), pixel_scales=(0.5, 0.5), centre=(0.0, 0.0))
38+ >>> print(centres_scaled)
39+ (0.0, 0.0)
3840 """
39- return (
40- 0.5 * (shape_native [0 ] - 1 ) - (centre [0 ] / pixel_scales [0 ]),
41- 0.5 * (shape_native [1 ] - 1 ) + (centre [1 ] / pixel_scales [1 ]),
42- )
41+
42+ # Calculate scaled y-coordinate by centering and adjusting for pixel scale
43+ y_scaled = 0.5 * (shape_native [0 ] - 1 ) - (centre [0 ] / pixel_scales [0 ])
44+
45+ # Calculate scaled x-coordinate by centering and adjusting for pixel scale
46+ x_scaled = 0.5 * (shape_native [1 ] - 1 ) + (centre [1 ] / pixel_scales [1 ])
47+
48+ # Return the scaled (y, x) coordinates
49+ return (y_scaled , x_scaled )
4350
4451
4552def mask_2d_circular_from (
@@ -70,16 +77,23 @@ def mask_2d_circular_from(
7077
7178 Examples
7279 --------
73- mask = mask_2d_circular_from(shape_native=(10, 10), pixel_scales=(0.1, 0.1), radius=0.5, centre=(0.0, 0.0))
80+ >>> mask = mask_2d_circular_from(shape_native=(10, 10), pixel_scales=(0.1, 0.1), radius=0.5, centre=(0.0, 0.0))
7481 """
82+
83+ # Get scaled coordinates of the mask center
7584 centres_scaled = mask_2d_centres_from (shape_native , pixel_scales , centre )
7685
86+ # Create a grid of y, x indices for the mask
7787 y , x = np .ogrid [: shape_native [0 ], : shape_native [1 ]]
88+
89+ # Scale the y and x indices based on pixel scales
7890 y_scaled = (y - centres_scaled [0 ]) * pixel_scales [0 ]
7991 x_scaled = (x - centres_scaled [1 ]) * pixel_scales [1 ]
8092
93+ # Compute squared distances from the center for each pixel
8194 distances_squared = x_scaled ** 2 + y_scaled ** 2
8295
96+ # Return a mask with True for pixels outside the circle and False for inside
8397 return distances_squared >= radius ** 2
8498
8599
@@ -114,18 +128,25 @@ def mask_2d_circular_annular_from(
114128
115129 Examples
116130 --------
117- mask = mask_2d_circular_annular_from(
118- shape_native=(10, 10), pixel_scales=(0.1, 0.1), inner_radius=0.5, outer_radius=1.5, centre=(0.0, 0.0)
119- )
131+ >>> mask = mask_2d_circular_annular_from(
132+ >>> shape_native=(10, 10), pixel_scales=(0.1, 0.1), inner_radius=0.5, outer_radius=1.5, centre=(0.0, 0.0)
133+ >>> )
120134 """
135+
136+ # Get scaled coordinates of the mask center
121137 centres_scaled = mask_2d_centres_from (shape_native , pixel_scales , centre )
122138
139+ # Create grid of y, x indices for the mask
123140 y , x = np .ogrid [: shape_native [0 ], : shape_native [1 ]]
141+
142+ # Scale the y and x indices based on pixel scales
124143 y_scaled = (y - centres_scaled [0 ]) * pixel_scales [0 ]
125144 x_scaled = (x - centres_scaled [1 ]) * pixel_scales [1 ]
126145
146+ # Compute squared distances from the center for each pixel
127147 distances_squared = x_scaled ** 2 + y_scaled ** 2
128148
149+ # Return the mask where pixels are unmasked between inner and outer radii
129150 return ~ (
130151 (distances_squared >= inner_radius ** 2 ) & (distances_squared <= outer_radius ** 2 )
131152 )
@@ -166,30 +187,31 @@ def mask_2d_elliptical_from(
166187
167188 Examples
168189 --------
169- mask = mask_2d_elliptical_from(
170- shape_native=(10, 10), pixel_scales=(0.1, 0.1), major_axis_radius=0.5, axis_ratio=0.5, angle=45.0, centre=(0.0, 0.0)
171- )
190+ >>> mask = mask_2d_elliptical_from(
191+ >>> shape_native=(10, 10), pixel_scales=(0.1, 0.1), major_axis_radius=0.5, axis_ratio=0.5, angle=45.0, centre=(0.0, 0.0)
192+ >>> )
172193 """
194+
195+ # Get scaled coordinates of the mask center
173196 centres_scaled = mask_2d_centres_from (shape_native , pixel_scales , centre )
174197
198+ # Create grid of y, x indices for the mask
175199 y , x = np .ogrid [: shape_native [0 ], : shape_native [1 ]]
200+
201+ # Scale the y and x indices based on pixel scales
176202 y_scaled = (y - centres_scaled [0 ]) * pixel_scales [0 ]
177203 x_scaled = (x - centres_scaled [1 ]) * pixel_scales [1 ]
178204
179- # Rotate the coordinates by the angle (counterclockwise)
180-
205+ # Compute the rotated coordinates and elliptical radius
181206 r_scaled = np .sqrt (x_scaled ** 2 + y_scaled ** 2 )
182-
183207 theta_rotated = np .arctan2 (y_scaled , x_scaled ) + np .radians (angle )
184-
185208 y_scaled_elliptical = r_scaled * np .sin (theta_rotated )
186209 x_scaled_elliptical = r_scaled * np .cos (theta_rotated )
187-
188- # Compute the elliptical radius
189210 r_scaled_elliptical = np .sqrt (
190211 x_scaled_elliptical ** 2 + (y_scaled_elliptical / axis_ratio ) ** 2
191212 )
192213
214+ # Return the mask where pixels are outside the elliptical region
193215 return ~ (r_scaled_elliptical <= major_axis_radius )
194216
195217
@@ -231,7 +253,7 @@ def mask_2d_elliptical_annular_from(
231253 The rotation angle of the outer ellipse within which pixels are unmasked, (counter-clockwise from the
232254 positive x-axis).
233255 centre
234- The centre of the elliptical annuli used to mask pixels.
256+ The centre of the elliptical annuli used to mask pixels.
235257
236258 Returns
237259 -------
@@ -240,95 +262,119 @@ def mask_2d_elliptical_annular_from(
240262
241263 Examples
242264 --------
243- mask = mask_elliptical_annuli_from(
244- shape=(10, 10), pixel_scales=(0.1, 0.1),
245- inner_major_axis_radius=0.5, inner_axis_ratio=0.5, inner_phi=45.0,
246- outer_major_axis_radius=1.5, outer_axis_ratio=0.8, outer_phi=90.0,
247- centre=(0.0, 0.0))
265+ >>> mask = mask_elliptical_annuli_from(
266+ >>> shape=(10, 10), pixel_scales=(0.1, 0.1),
267+ >>> inner_major_axis_radius=0.5, inner_axis_ratio=0.5, inner_phi=45.0,
268+ >>> outer_major_axis_radius=1.5, outer_axis_ratio=0.8, outer_phi=90.0,
269+ >>> centre=(0.0, 0.0)
270+ >>> )
248271 """
272+
273+ # Get scaled coordinates of the mask center
249274 centres_scaled = mask_2d_centres_from (shape_native , pixel_scales , centre )
250275
276+ # Create grid of y, x indices for the mask
251277 y , x = np .ogrid [: shape_native [0 ], : shape_native [1 ]]
278+
279+ # Scale the y and x indices based on pixel scales
252280 y_scaled = (y - centres_scaled [0 ]) * pixel_scales [0 ]
253281 x_scaled = (x - centres_scaled [1 ]) * pixel_scales [1 ]
254282
255- # Rotate the coordinates for the inner annulus
283+ # Compute and rotate coordinates for inner annulus
256284 r_scaled_inner = np .sqrt (x_scaled ** 2 + y_scaled ** 2 )
257285 theta_rotated_inner = np .arctan2 (y_scaled , x_scaled ) + np .radians (inner_phi )
258286 y_scaled_elliptical_inner = r_scaled_inner * np .sin (theta_rotated_inner )
259287 x_scaled_elliptical_inner = r_scaled_inner * np .cos (theta_rotated_inner )
260-
261- # Compute the elliptical radius for the inner annulus
262288 r_scaled_elliptical_inner = np .sqrt (
263289 x_scaled_elliptical_inner ** 2
264290 + (y_scaled_elliptical_inner / inner_axis_ratio ) ** 2
265291 )
266292
267- # Rotate the coordinates for the outer annulus
293+ # Compute and rotate coordinates for outer annulus
268294 r_scaled_outer = np .sqrt (x_scaled ** 2 + y_scaled ** 2 )
269295 theta_rotated_outer = np .arctan2 (y_scaled , x_scaled ) + np .radians (outer_phi )
270296 y_scaled_elliptical_outer = r_scaled_outer * np .sin (theta_rotated_outer )
271297 x_scaled_elliptical_outer = r_scaled_outer * np .cos (theta_rotated_outer )
272-
273- # Compute the elliptical radius for the outer annulus
274298 r_scaled_elliptical_outer = np .sqrt (
275299 x_scaled_elliptical_outer ** 2
276300 + (y_scaled_elliptical_outer / outer_axis_ratio ) ** 2
277301 )
278302
303+ # Return the mask where pixels are outside the inner and outer elliptical annuli
279304 return ~ (
280305 (r_scaled_elliptical_inner >= inner_major_axis_radius )
281306 & (r_scaled_elliptical_outer <= outer_major_axis_radius )
282307 )
283308
284309
285310def mask_2d_via_pixel_coordinates_from (
286- shape_native : Tuple [int , int ], pixel_coordinates : [ list ] , buffer : int = 0
311+ shape_native : Tuple [int , int ], pixel_coordinates : list , buffer : int = 0
287312) -> np .ndarray :
288313 """
289- Returns a mask where all unmasked `False` entries are defined from an input list of list of pixel coordinates.
314+ Returns a mask where all unmasked `False` entries are defined from an input list of 2D pixel coordinates.
290315
291- These may be buffed via an input `` buffer`` , whereby all entries in all 8 neighboring directions by this
316+ These may be buffed via an input `buffer`, whereby all entries in all 8 neighboring directions are buffed by this
292317 amount.
293318
294319 Parameters
295320 ----------
296- shape_native (int, int)
297- The (y,x) shape of the mask in units of pixels.
298- pixel_coordinates : [[int, int]]
299- The input lists of 2D pixel coordinates where `False` entries are created.
321+ shape_native
322+ The (y, x) shape of the mask in units of pixels.
323+ pixel_coordinates
324+ The input list of 2D pixel coordinates where `False` entries are created.
300325 buffer
301- All input `` pixel_coordinates` ` are buffed with `False` entries in all 8 neighboring directions by this
326+ All input `pixel_coordinates` are buffed with `False` entries in all 8 neighboring directions by this
302327 amount.
303- """
304328
305- mask_2d = np .full (shape = shape_native , fill_value = True )
329+ Returns
330+ -------
331+ np.ndarray
332+ The 2D mask array where all entries in the input pixel coordinates are set to `False`, with optional buffering
333+ applied to the neighboring entries.
306334
307- for y , x in pixel_coordinates :
335+ Examples
336+ --------
337+ mask = mask_2d_via_pixel_coordinates_from(
338+ shape_native=(10, 10),
339+ pixel_coordinates=[[1, 2], [3, 4], [5, 6]],
340+ buffer=1
341+ )
342+ """
343+ mask_2d = np .full (
344+ shape = shape_native , fill_value = True
345+ ) # Initialize mask with all True values
346+
347+ for (
348+ y ,
349+ x ,
350+ ) in (
351+ pixel_coordinates
352+ ): # Loop over input coordinates to set corresponding mask entries to False
308353 mask_2d [y , x ] = False
309354
310- if buffer == 0 :
355+ if buffer == 0 : # If no buffer is specified, return the mask directly
311356 return mask_2d
312- return buffed_mask_2d_from (mask_2d = mask_2d , buffer = buffer )
357+ return buffed_mask_2d_from (mask_2d = mask_2d , buffer = buffer ) # Apply buf
313358
314359
315360import numpy as np
316361
317362
318363def min_false_distance_to_edge (mask : np .ndarray ) -> Tuple [int , int ]:
319364 """
320- Compute the minimum 1D distance in the y and x directions from any False value at the mask's extreme positions
365+ Compute the minimum 1D distance in the y and x directions from any ` False` value at the mask's extreme positions
321366 (leftmost, rightmost, topmost, bottommost) to its closest edge.
322367
323368 Parameters
324369 ----------
325370 mask
326- A 2D boolean array where False represents the unmasked region.
371+ A 2D boolean array where ` False` represents the unmasked region.
327372
328373 Returns
329374 -------
330- The smallest distances of any extreme False value to the nearest edge in the vertical (y) and horizontal (x)
331- directions.
375+ Tuple[int, int]
376+ The smallest distances of any extreme `False` value to the nearest edge in the vertical (y) and horizontal (x)
377+ directions.
332378
333379 Examples
334380 --------
@@ -341,27 +387,40 @@ def min_false_distance_to_edge(mask: np.ndarray) -> Tuple[int, int]:
341387 >>> min_false_distance_to_edge(mask)
342388 (1, 1)
343389 """
344- false_indices = np .column_stack (np .where (mask == False ))
390+ false_indices = np .column_stack (
391+ np .where (mask == False )
392+ ) # Find all coordinates where mask is False
345393
346394 if false_indices .size == 0 :
347- raise ValueError ("No False values found in the mask." )
348-
349- leftmost = false_indices [np .argmin (false_indices [:, 1 ])]
350- rightmost = false_indices [np .argmax (false_indices [:, 1 ])]
351- topmost = false_indices [np .argmin (false_indices [:, 0 ])]
352- bottommost = false_indices [np .argmax (false_indices [:, 0 ])]
353-
354- height , width = mask .shape
395+ raise ValueError (
396+ "No False values found in the mask."
397+ ) # Raise error if no False values
398+
399+ leftmost = false_indices [
400+ np .argmin (false_indices [:, 1 ])
401+ ] # Find the leftmost False coordinate
402+ rightmost = false_indices [
403+ np .argmax (false_indices [:, 1 ])
404+ ] # Find the rightmost False coordinate
405+ topmost = false_indices [
406+ np .argmin (false_indices [:, 0 ])
407+ ] # Find the topmost False coordinate
408+ bottommost = false_indices [
409+ np .argmax (false_indices [:, 0 ])
410+ ] # Find the bottommost False coordinate
411+
412+ height , width = mask .shape # Get the height and width of the mask
355413
356414 # Compute distances to respective edges
357415 left_dist = leftmost [1 ] # Distance to left edge (column index)
358416 right_dist = width - 1 - rightmost [1 ] # Distance to right edge
359417 top_dist = topmost [0 ] # Distance to top edge (row index)
360418 bottom_dist = height - 1 - bottommost [0 ] # Distance to bottom edge
361419
362- # Return the minimum distance to an edge
420+ # Return the minimum distance to both edges
363421 return min (top_dist , bottom_dist ), min (left_dist , right_dist )
364422
423+
365424def blurring_mask_2d_from (
366425 mask_2d : np .ndarray , kernel_shape_native : Tuple [int , int ]
367426) -> np .ndarray :
@@ -397,27 +456,47 @@ def blurring_mask_2d_from(
397456
398457 """
399458
400- y_distance , x_distance = min_false_distance_to_edge (mask_2d )
459+ # Get the distance from False values to edges
460+ y_distance , x_distance = min_false_distance_to_edge (
461+ mask_2d
462+ )
401463
402- y_kernel_distance = (kernel_shape_native [0 ]) // 2
403- x_kernel_distance = (kernel_shape_native [1 ]) // 2
464+ # Compute kernel half-size in y and x direction
465+ y_kernel_distance = (
466+ kernel_shape_native [0 ]
467+ ) // 2
468+ x_kernel_distance = (
469+ kernel_shape_native [1 ]
470+ ) // 2
404471
472+ # Check if mask is too small for the kernel size
405473 if (y_distance < y_kernel_distance ) or (x_distance < x_kernel_distance ):
406-
407474 raise exc .MaskException (
408475 "The input mask is too small for the kernel shape. "
409476 "Please pad the mask before computing the blurring mask."
410477 )
411478
412- kernel = np .ones (kernel_shape_native , dtype = np .uint8 )
479+ # Create a kernel with the given PSF shape
480+ kernel = np .ones (
481+ kernel_shape_native , dtype = np .uint8
482+ )
413483
414- convolved_mask = convolve (mask_2d .astype (np .uint8 ), kernel , mode = "reflect" , cval = 0 )
484+ # Convolve mask with kernel producing non-zero values around mask False values
485+ convolved_mask = convolve (
486+ mask_2d .astype (np .uint8 ), kernel , mode = "reflect" , cval = 0
487+ )
415488
416- result_mask = convolved_mask == np .prod (kernel_shape_native )
489+ # Identify pixels that are non-zero and fully covered by kernel
490+ result_mask = convolved_mask == np .prod (
491+ kernel_shape_native
492+ )
417493
418- blurring_mask = ~ mask_2d + result_mask
494+ # Create the blurring mask by removing False values in original mask
495+ blurring_mask = (
496+ ~ mask_2d + result_mask
497+ )
419498
420- return blurring_mask
499+ return blurring_mask
421500
422501
423502@numba_util .jit ()
0 commit comments