Source code for eloy.psf

"""
Point Spread Function (PSF) modeling utilities.

This module provides functions for estimating PSF parameters using image moments
and fitting 2D Gaussian models to image data.
"""

from scipy.optimize import minimize
import numpy as np

[docs] gaussian_sigma_to_fwhm = 2.0 * np.sqrt(2.0 * np.log(2.0))
[docs] def moments(data): """ Estimate the moments of a 2D distribution. Parameters ---------- data : np.ndarray 2D image data. Returns ------- dict Dictionary of estimated parameters: amplitude, x, y, sigma_x, sigma_y, background, theta, beta. """ height = data.max() background = data.min() data = data - np.min(data) total = data.sum() x, y = np.indices(data.shape) x = (x * data).sum() / total y = (y * data).sum() / total col = data[:, int(y)] width_x = np.sqrt(abs((np.arange(col.size) - y) ** 2 * col).sum() / col.sum()) row = data[int(x), :] width_y = np.sqrt(abs((np.arange(row.size) - x) ** 2 * row).sum() / row.sum()) width_x /= gaussian_sigma_to_fwhm width_y /= gaussian_sigma_to_fwhm return { "amplitude": height, "x": x, "y": y, "sigma_x": width_x, "sigma_y": width_y, "background": background, "theta": 0.0, "beta": 3.0, }
[docs] def fit_gaussian(data, init=None): """ Fit a 2D Gaussian to the data. Parameters ---------- data : np.ndarray 2D image data. init : dict or None, optional Initial parameter estimates. Returns ------- dict Dictionary of fitted parameters: amplitude, x, y, sigma_x, sigma_y, theta, background. """ x, y = np.indices(data.shape) def model(height, xo, yo, sx, sy, theta, m): dx = x - xo dy = y - yo a = (np.cos(theta) ** 2) / (2 * sx**2) + (np.sin(theta) ** 2) / (2 * sy**2) b = -(np.sin(2 * theta)) / (4 * sx**2) + (np.sin(2 * theta)) / (4 * sy**2) c = (np.sin(theta) ** 2) / (2 * sx**2) + (np.cos(theta) ** 2) / (2 * sy**2) psf = height * np.exp(-(a * dx**2 + 2 * b * dx * dy + c * dy**2)) return psf + m def nll(params): ll = np.sum(np.power(model(*params) - data, 2)) return ll keys = ["amplitude", "x", "y", "sigma_x", "sigma_y", "theta", "background"] if init is None: p0 = moments(data) else: p0 = init p0 = [p0[k] for k in keys] w = np.max(data.shape) bounds = [ (0, 1.5), *((0, w),) * 2, *((0.5, w),) * 2, (-np.pi, np.pi), (0, np.mean(data)), ] opt = minimize(nll, p0, bounds=bounds).x return {k: v for k, v in zip(keys, opt)}