Source code for viscube.windows
import numpy as np
from typing import Optional
from scipy.special import i0 # Modified Bessel I0
import sys
# ---- CASA/Schwab 1984 spheroidal rational approximation coefficients ----
_SPH_A = np.array([0.01624782, -0.05350728, 0.1464354, -0.2347118,
0.2180684, -0.09858686, 0.01466325], dtype=float) # a6..a0
_SPH_B1 = 0.2177793 # denominator linear coeff
def _spheroid_vec(eta: np.ndarray) -> np.ndarray:
"""Vectorized prolate-spheroidal rational approximation (valid for |eta|<=1)."""
out = np.zeros_like(eta, dtype=float)
mask = np.abs(eta) <= 1.0
if np.any(mask):
n = eta[mask]**2 - 1.0
a6, a5, a4, a3, a2, a1, a0 = _SPH_A
num = ((((((a6*n + a5)*n + a4)*n + a3)*n + a2)*n + a1)*n + a0)
den = _SPH_B1 * n + 1.0
out[mask] = num / den
return out
[docs]
def kaiser_bessel_window(u,
center: float,
*,
pixel_size: float = 0.015,
m: int = 6,
beta: Optional[float] = None,
normalize: bool = True) -> np.ndarray:
"""
1D Kaiser–Bessel interpolation window (separable in u, v).
Parameters
----------
u : array-like
Coordinates (same units as center).
center : float
Grid-cell center coordinate.
pixel_size : float
Grid pixel size in uv units.
m : int
*Total* support width in pixel units (covers m * pixel_size).
Effective half-width = 0.5 * m * pixel_size.
beta : float, optional
Shape parameter. If None, use a reasonable default for oversamp≈2:
beta ≈ π * sqrt( (m/2)^2 - 0.8 )
normalize : bool
If True, normalize so that window(center) == 1.
Returns
-------
w : ndarray (same shape as u)
"""
u = np.asarray(u, dtype=float)
half_width = 0.5 * m * pixel_size
dist = np.abs(u - center)
w = np.zeros_like(u, dtype=float)
mask = dist <= half_width
if not np.any(mask):
return w
if beta is None:
beta = np.pi * np.sqrt((m / 2.0)**2 - 0.8)
t = dist[mask] / half_width # in [0,1]
arg = beta * np.sqrt(1.0 - t**2)
denom = i0(beta)
w_vals = i0(arg) / denom
if normalize and w_vals.size:
w0 = w_vals.max()
if w0 > 0:
w_vals = w_vals / w0
w[mask] = w_vals
if np.any(w < 0):
print("Error: negative weights")
print(f"U values: {u}")
print(f"Center values: {center}")
print(f"Beta values: {beta}")
print(f"W values: {w}")
print(f"Denom values: {denom}")
sys.exit()
return w
[docs]
def casa_pswf_window(u,
center: float,
*,
pixel_size: float = 0.015,
m: int = 5,
normalize: bool = True) -> np.ndarray:
"""
CASA/Schwab prolate-spheroidal gridding kernel (1-D separable form).
Parameters
----------
u : array-like
center : float
pixel_size : float
m : int
Total *integer* support (number of pixels). Typical choice: m=5.
normalize : bool
Normalize so that peak value at center is 1.
Returns
-------
w : ndarray
"""
u = np.asarray(u, dtype=float)
dpix = (u - center) / pixel_size # distance in pixels
half_width = m / 2.0 # in pixels
eta = dpix / half_width # maps support to |eta|<=1
w = (1.0 - eta**2) * _spheroid_vec(eta)
w[np.abs(eta) > 1.0] = 0.0
if normalize:
w0 = _spheroid_vec(np.array([0.0]))[0]
if w0 != 0:
w /= w0
return w
[docs]
def pillbox_window(u, center: float, pixel_size: float = 0.015, m: int = 1):
"""Boxcar window with total width m * pixel_size."""
u = np.asarray(u, dtype=float)
half = 0.5 * m * pixel_size
return (np.abs(u - center) <= half).astype(float)
[docs]
def sinc_window(u, center: float, pixel_size: float = 0.015, m: int = 1):
"""Sinc window truncated by m * pixel_size (soft truncation)."""
u = np.asarray(u, dtype=float)
# Use normalized sinc: np.sinc(x) = sin(pi x)/(pi x)
return np.sinc((u - center) / (m * pixel_size))