Quickstart aperture sums

This notebook measures a synthetic source with exact circular aperture aperture summation. The point is to show the shortest useful workflow: create an aperture, call apsum_exact(), and interpret the returned flux and effective pixel area.

import numpy as np
import matplotlib.pyplot as plt
import astroapers as aap

Make a small image

The source center is in (x, y) order, matching Photutils and SEP pixel coordinates.

ny, nx = 30, 40
y, x = np.mgrid[:ny, :nx]

x0, y0 = 15.2, 17.6
background = 12.0

# make a Gaussian source
amplitude = 150.0
sigma = 3.0
source = amplitude * np.exp(-0.5 * ((x - x0) ** 2 + (y - y0) ** 2) / sigma**2)
data = background + source
fig, ax = plt.subplots(figsize=(5, 4))
ax.imshow(data, origin="lower", cmap="viridis")
ax.set_title("Synthetic image")
ax.set_xlabel("x pixel")
ax.set_ylabel("y pixel")
plt.show()

Measure an aperture sum

CircAp.apsum_exact() uses exact fractional pixel overlap for circular apertures. It returns the weighted sum and the in-frame weighted pixel count.

ap = aap.CircAp((x0, y0), r=5.0)
apsum, npix = ap.apsum_exact(data, return_npix=True)

apsum, npix, ap.area, npix == ap.area
(array(7283.74585208), array(78.53981634), 78.53981633974483, np.True_)

The source flux above a known constant background is:

# Use a known background level for this synthetic example.
srcsum = apsum - background * npix
print(f"Net flux: {srcsum:.2f} = apsum({apsum:.2f}) - background({background:.2f}) * npix({npix:.2f})")
Net flux: 6341.27 = apsum(7283.75) - background(12.00) * npix(78.54)

Overlay the aperture

fig, ax = plt.subplots(figsize=(5, 4))
ax.imshow(data, origin="lower", cmap="viridis")
ap.plot(ax=ax, color="r", lw=1.5)
ax.set(title="CircAp.plot()")
plt.show()

Maximum Performance

For fixed production code, import the raw Rust extension as aapr. The raw functions expect contiguous arrays and positional geometry arguments. They do not provide masks, validation, keyword arguments, or Python result shaping. For more usage patterns, inspect astroapers.kernels, which is the Python layer that calls _rust internally.

import astroapers._rust as aapr

raw_data = np.ascontiguousarray(data, dtype=np.float64)
xpos = np.ascontiguousarray([x0], dtype=np.float64)
ypos = np.ascontiguousarray([y0], dtype=np.float64)

raw_apsum, raw_npix = aapr.apsum_circ_exact(raw_data, xpos, ypos, 5.0)
raw_sum_only = aapr.apsum_circ_exact_sum(raw_data, xpos, ypos, 5.0)

assert np.allclose(raw_apsum[0], apsum)
assert np.allclose(raw_npix[0], npix)
assert np.allclose(raw_sum_only[0], apsum)

raw_apsum[0], raw_npix[0], raw_sum_only[0]
(7283.745852078197, 78.53981633974483, 7283.745852078197)