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 C-contiguous arrays and positional geometry arguments. They enforce basic input checks, including dtype and layout requirements, but do not provide the high-level Python validation, masks, keyword arguments, or 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)