@@ -263,7 +263,7 @@ def ellipse_params_via_border_pca_from(border_grid, xp=np, eps=1e-12):
263263 return origin , a , b , phi
264264
265265
266- def relocated_grid_via_ellipse_border_from (grid , origin , a , b , phi , xp = np , eps = 1e-12 ):
266+ def relocated_grid_via_ellipse_border_from (grid , origin , a , b , phi , xp = np , border_frac = 1e-3 ):
267267 """
268268 Rotated ellipse centered at origin with semi-axes a (major, x'), b (minor, y'),
269269 rotated by phi radians (counterclockwise).
@@ -284,28 +284,32 @@ def relocated_grid_via_ellipse_border_from(grid, origin, a, b, phi, xp=np, eps=1
284284 Numerical safety epsilon.
285285 """
286286
287- # shift to origin
288287 dy = grid [:, 0 ] - origin [0 ]
289288 dx = grid [:, 1 ] - origin [1 ]
290289
291290 c = xp .cos (phi )
292291 s = xp .sin (phi )
293292
294- # rotate into ellipse-aligned frame
295293 xprime = c * dx + s * dy
296294 yprime = - s * dx + c * dy
297295
298- # ellipse radius in normalized coords
299296 q = (xprime / a ) ** 2 + (yprime / b ) ** 2
300297
301298 outside = q > 1.0
302- scale = 1.0 / xp .sqrt (xp .maximum (q , 1.0 + eps ))
303299
304- # scale back to boundary
300+ # Target radius in normalized coords (slightly inside 1.0)
301+ # Using squared target so it matches q's definition.
302+ shrink = xp .asarray (1.0 - border_frac , dtype = q .dtype )
303+ q_target = shrink * shrink # (1 - border_frac)^2
304+
305+ # Project outside points to q = q_target
306+ # scale^2 * q = q_target -> scale = sqrt(q_target / q)
307+ safe_q = xp .maximum (q , xp .asarray (1.0 , dtype = q .dtype )) # outside => q>=1; inside => 1
308+ scale = xp .sqrt (q_target / safe_q )
309+
305310 xprime2 = xprime * scale
306311 yprime2 = yprime * scale
307312
308- # rotate back to original frame
309313 dx2 = c * xprime2 - s * yprime2
310314 dy2 = s * xprime2 + c * yprime2
311315
0 commit comments