Calibrated aperture photometry on linear astronomical data: catalog-anchored integrated magnitudes, surface brightness in mag/arcsec², robust background subtraction with placement verification, and direct AAVSO Extended-format export.
At its core the tool solves one equation per channel from a field of catalog stars — the photometric zero point that relates measured instrumental magnitudes to a real stellar magnitude system — and then uses that zero point to turn an arbitrary user-defined region of the sky into a calibrated magnitude and surface brightness. It operates exclusively on linear data (no stretches, no clipping), carries full uncertainty propagation from the per-star ZP scatter and background noise all the way to the reported ±3σ, and verifies the result by re-measuring your target against four independently-placed background boxes.
Measurement always runs in the same order:
Each step produces output you can inspect before continuing. Step 2 opens a scatter plot of catalog mag vs instrumental mag with the fit line and clipped points marked — the plot is your calibration's reality check (the Sh 2-68 paper's Figure 2 is this exact plot for three narrowband frames). Step 4 opens a non-blocking results dialog with every number you need, including a Bortle-scale estimate of your sky quality.
Click Step 1: Fetch Catalog Stars. The tool reads the active document's WCS header, builds a field center and radius from the image corners, and queries catalogs in this order:
The result is a single merged star list cached into the document's metadata under SFCC_star_list (shared with the Spectral Flux Color Calibration tool). Subsequent runs on the same document skip the catalog query entirely and use the cache. Use Clear All to invalidate it.
Click Step 2: Compute Zero Points. The tool:
Three settings govern this stage:
Afterward, click Show ZP Graphs to open a per-channel scatter plot of catalog magnitude vs instrumental magnitude. The fit line is drawn as y = x + ZP, kept points are blue, clipped points are red. For a healthy calibration this looks like a tight straight line across six to eight magnitudes of dynamic range (as in the whitepaper's Figure 2 for all three narrowband frames). A visibly nonlinear plot — sagging at the bright end, hooking at the faint end — means saturation or detection noise is biasing the fit, and the measurement should be redone with revised SEP settings.
Click Step 3: Pick Target Region to open the region picker. The picker shows your image (optionally autostretched for visibility — the stretch is display-only, your measurement uses the linear pixels) and offers four selection modes selected from the dropdown at the top:
Switch to Star (auto) and the picker exposes two extra controls:
The algorithm: SEP runs on a cutout around your click with a decreasing detection threshold until a source is found; the nearest source to the click wins. The second-moment axes a, b, and position angle θ give the aperture's shape. The half-flux radius is measured directly from the curve of growth; the capture-fraction radius is read from the same curve at your chosen flux percentile. The ellipse is scaled area-preservingly to that radius and drawn on the image for inspection. Re-click to try a different source, or adjust Capture% and re-click to re-fit.
Instead of clicking in the picker, you can type a name into the Find a star by name field on the main dialog and press Enter. SIMBAD resolves the name, the tool projects RA/Dec to pixels through the WCS, confirms the star falls on the frame, opens the picker in Star (auto) mode, and auto-fits it on the first attempt. For AAVSO variable-star submission this is faster than clicking, especially on dense fields.
Save Region stores the current target as a sky polygon (RA/Dec coordinates), tagged with the OBJECT header value as its default name. Load Region reprojects a saved region through the current image's WCS and rasterizes it onto this frame.
Because the storage format is in sky coordinates, the same physical patch of the nebula is measured identically across different stacks of the same object — different nights, different filters, different subs, re-registered or re-cropped. In the Sh 2-68 whitepaper this is how the Hα coma was re-measured against two independent background placements with aperture-level agreement to 0.004 mag (Table 2 of the paper). Regions densify to ~64 vertices on save, so distortion-corrected (SIP) WCS solutions re-project faithfully: a straight edge in pixel space on image A becomes a slightly curved edge on image B's pixel grid, exactly as the physics demands.
Click Step 4: Measure Object Region. The tool combines the target mask with the background mask (automatically placed by auto_rect_box if you haven't drawn one), computes net flux via scaled background subtraction, propagates uncertainty from the background noise and the ZP SEM, applies the systematic floor, and opens a non-blocking results dialog.
For a mono image the results dialog reports:
For an RGB image the same quantities are reported per channel: m_R, m_G, m_B and µ_R, µ_G, µ_B, each with its own ±3σ. The Bortle estimate comes from the green channel.
The dialog is non-modal — you can leave it open while you tweak the region or roll the systematic floor and remeasure. Use Copy to Clipboard to paste the full report into an observation log or paper.
The Systematic floor setting (mag, 0-1.0, default 0.100) is a catch-all for calibration uncertainties not captured by photon and background statistics: ZP colour term, narrowband-to-broadband mapping, flat-field residuals, and the sensitivity of the result to background placement. The reported ± is 3× the combined uncertainty:
A value of 0.100 mag is defensible for calibrated narrowband surface photometry — this is the value used in the Sh 2-68 paper. Raise it (0.15-0.20) if your calibration is less certain (e.g. small ZP ensemble, visible non-linearity in the ZP plot); lower it only with explicit justification.
Check Verify against 4-quadrant backgrounds before measuring and the results dialog gains an extra section: the target is re-measured against the primary background box plus an independent 50×50 (or your sized) box automatically placed in each of the four image quadrants. The four quadrant boxes are chosen as the darkest source-free region in each quadrant that does not overlap with the primary background box's keep-out zone, so the quadrant placements can never duplicate your primary.
The section reports µ measured against each background, their spread (max − min), and a verdict: ROBUST if the maximum deviation from the primary measurement sits within the 3σ error envelope, SENSITIVE if it exceeds it. "SENSITIVE" is not necessarily wrong — it tells you the measurement depends on where you drew the background box, which may itself be a physical reality (an uneven gradient, a nebula filling most of the frame). "ROBUST" is a strong defence of the quoted value: the Sh 2-68 whitepaper's two-background Hα coma check (0.004 mag agreement, far inside the 3σ envelope) is exactly this check surfaced as a published number.
The Full Image View button opens an annotated full-frame overview showing the target as a red mask overlay with an adjustable opacity, the primary background box in gold, and (toggleable) the four qualifying quadrant backgrounds in cyan. This is the figure you want in a whitepaper's aperture-placement section: it unambiguously documents where every number came from. Save as PNG/TIFF or copy straight to the clipboard.
The right-column AAVSO panel wraps the AAVSO Extended-format upload workflow around the measurement engine. The sequence is:
The exported file carries 1σ statistical errors (per AAVSO convention), not the 3σ totals the results dialog shows. Your observer code is remembered between sessions. The filter mapping prefers the FITS FILTER card when it maps confidently (standard Johnson/Cousins or Sloan letters, Astrodon-labeled filters, "g'" and similar); when it's ambiguous you're prompted with a dropdown pre-selected to the best guess.
The main dialog's right column shows a live crop around your current target region, with the mask drawn as a red translucent overlay. Two controls govern it: Overlay opacity (0-1.0, default 0.25) and ROI padding (0-200 px around the mask bbox, default 20). The crop stretches for display — it never affects the measurement. This is the fastest way to confirm you're measuring the correct region before clicking Measure.
The Full Image View button opens the whole frame with the same overlays plus the primary and (toggleable) qualifying backgrounds drawn in. This is the publication-ready figure.
Clear All wipes everything: cached stars, computed zero points, drawn regions, and the metadata-cached SIMBAD star list on the active document. Use when the WCS has changed (re-solve, crop, resample) and you need the catalog cross-match done fresh.
The math behind every number the tool reports.
Target measurement is a straightforward integration over the mask Ωobj with a scaled-area background subtraction from a disjoint mask Ωbg:
where Nobj and Nbg are pixel counts. The ratio Nobj / Nbg scales the background sum to the object's area regardless of their relative sizes — one of them can be a 500-pixel freehand lasso and the other a 50×50 box without any correction gymnastics.
The background noise is measured robustly on the background mask via the Median Absolute Deviation:
The 1.4826 factor converts MAD to a standard-deviation equivalent under Gaussian noise assumptions. MAD is used instead of a plain standard deviation because any real stars that leak into the background box inflate std but barely move MAD, which is exactly what we want: the σ we quote should reflect the sky, not whatever happens to be sitting in the box.
The flux-level uncertainty assumes photon-dominated Poisson statistics on each pixel, which sum in quadrature over the object area:
For each matched catalog star the aperture radii are set from the SEP second-moment axes a and b, with the FWHM computed from their geometric mean to handle elliptical PSFs:
rap is clamped to [2.0, 15.0] px, and rin, rout are guaranteed to grow strictly outward from rap. Per-star sizing matters: a bright well-focused star and a dim near-saturation one carry very different FWHM, and a one-size-fits-all aperture systematically under-reads one or over-reads the other.
Local background is estimated as the mean flux per pixel in the annulus rin-rout:
and the star's net flux is the aperture sum minus the annulus-level background scaled to the aperture area:
SEP's sum_circle and sum_circann handle fractional-pixel aperture coverage at 5× subpixel sampling, which keeps aperture correction below the 0.01 mag level for well-sampled PSFs.
For each star with net flux Fi > 0 and a catalog magnitude mcat,i in the chosen band, one ZP candidate is formed:
This is the ZP such that mcat,i = minst,i + ZP, where minst = −2.5 log10(F). A sigma-clipping iteration (up to 3 rounds, threshold set by the ZP sigma-clip parameter, default 2.5σ) refines the surviving set:
The uncertainty on the median is reported as the standard error of the mean:
Why the median rather than a weighted mean? With sigma-clipping already in play the surviving set is close to Gaussian around the true ZP, so median and mean converge, but the median remains robust against pathological ZP outliers from blended neighbours or badly-matched sources that happen to sneak through the sigma clip. Reported standard error is more conservative than any weighted mean would give.
Per-target error propagation combines the ZP standard error and the flux SNR in the standard way (δ(log10x)/δx = 1/(x ln 10)):
The integrated target magnitude is:
and the surface brightness in mag/arcsec² is obtained by normalizing the flux to the region's angular area:
equivalent to:
where the angular area is computed from the pixel count and the WCS pixel scale p (arcsec/px):
Pixel scale is read from the WCS celestial pixel-scale matrix as the length of its first column, in arcseconds. This is correct for a plate solution without significant distortion; for highly distorted fields (very wide-field, SIP-heavy) the per-pixel area varies slightly across the frame, and strictly the sum over Ωobj of per-pixel areas should be used — a sub-percent correction in practice that this tool does not currently apply.
For stars matched out of the local Gaia XP library, the catalog magnitudes used in the ZP fit are not pre-tabulated — they are synthesized from the star's stored BP/RP spectrum by integration through Johnson passbands. Given the flux density fλ(λ) on the Gaia wavelength grid and the Johnson transmission TX(λ) for X ∈ {B, V, R}:
The raw integrals are on an arbitrary instrumental scale (XP flux units) and are useless as absolute catalog magnitudes directly. To put them on a real magnitude system, each star is anchored to its own real Gaia G magnitude by first synthesizing a Gaia-G instrumental magnitude the same way:
and then computing a per-star offset to the real Gaia G from the Gaia DR3 catalog:
The same offset is applied to all three Johnson synthetic magnitudes:
Because the same multiplicative instrumental scale factor appears in all four integrals, dividing it out via C places B, V, R on a real magnitude system star-by-star. Any colour-dependent error in the synthetic Gaia-G passband enters as a systematic that is strongly correlated across all three channels — so it moves the whole ZP up or down together, which the ZP fit absorbs identically to a flat throughput factor. This is why the whitepaper's Figure 2 shows such tight linearity: the per-star anchoring handles the hardest part of making XP spectra behave as catalog photometry.
Gaia XP and SIMBAD results are merged by spatial cross-match at 2″:
computed via astropy's match_to_catalog_sky. Gaia XP is authoritative on position and photometry (uniform calibration across the ensemble matters more for a clean ZP than picking up individual measured mags from a different pipeline). SIMBAD contributes two things: entries with no XP counterpart within 2″ (bright saturated stars Gaia didn't register, well-catalogued objects, etc.), and non-photometric fields — main identifier, object type, spectral type, Pickles-match template — lifted onto XP matches that lacked them. If both tiers fail or the merged list is still too thin (fewer than 20 stars), APASS DR9 is queried as a last-resort fallback and merged the same way.
In Star (auto) picker mode, the aperture radius is set from the cumulative flux as a function of aperture radius centered on the star:
Two radii are extracted from the measured curve: the half-flux radius (HFR, used to set the search range and as a fallback) is F(rHFR) = ½ F(∞), and the aperture radius is the solution of:
where f is the user-chosen Capture %. SEP's flux_radius solves this numerically on the background-subtracted cutout; if the measurement fails (wings cut off, very faint star), the tool falls back to a Gaussian-equivalent scaling from the HFR using the inverse-CDF relation between rHFR and rcap. The ellipse is then scaled area-preservingly to rcap along the measured b/a ratio.
When you Save Region, the current pixel-space polygon is densified to ~64 vertices per edge, each vertex is projected through the active image's WCS to RA/Dec, and the resulting sky polygon is stored in a JSON region library at the user's config directory. On Load, the sky polygon is projected through the current image's WCS back to pixel space and rasterized to a mask via matplotlib's Path.contains_points at pixel centres (origin 0 to match astropy's convention).
Densification is the critical step: a straight edge in pixel space on image A is not a straight edge on image B's pixel grid if the two frames differ in rotation, scale, or SIP distortion coefficients. Sampling 64 points along each original edge preserves the shape of the sky polygon through the round-trip to within fractional-pixel accuracy, so a region measured on one frame of a time series lands on the same physical patch of sky on every other frame.
If a pixel scale is available and the FITS FILTER card does not match a narrowband token (Hα, OIII, SII, Hβ, "NB", "NARROWBAND", "DUOBAND", common duo-/tri-/quad-band products), the tool maps the measured background surface brightness to a Bortle class using the standard SQM→Bortle correspondence:
| Bortle | Label | Sky µ (mag/arcsec²) |
|---|---|---|
| 1 | Excellent | ≥ 21.99 |
| 2 | Typical dark | 21.89-21.99 |
| 3 | Rural | 21.69-21.89 |
| 4 | Rural/suburban | 20.49-21.69 |
| 5 | Suburban | 19.50-20.49 |
| 6 | Bright suburban | 18.94-19.50 |
| 7 | Suburban/urban | 18.38-18.94 |
| 8 | City | 17.80-18.38 |
| 9 | Inner city | < 17.80 |
The Bortle estimate is explicitly suppressed for narrowband frames because a 4.5 nm filter is not measuring the same quantity as an unfiltered sky-quality meter — a dark NB-filtered background can mask a mediocre visual site. The whitepaper quotes the site's visual Bortle rating separately from the frame's per-filter sky background measurement, exactly for this reason.
SIMBAD and APASS provide B, V, and R catalog magnitudes directly. Narrowband filters do not match any catalog band perfectly, so SASpro maps them to the closest broadband filter: Hα → R, [S II] → R, [O III] → V, Hβ → B. This is the convention used in the Sh 2-68 paper and is standard practice in narrowband surface photometry.
The approximation enters the error budget as a per-filter colour term: the catalog's B / V / R passbands are much wider than the narrowband filter, so stars of different spectral types have slightly different throughput ratios, and the ZP shifts by a star-colour-dependent amount. For an ensemble ZP fit over a diverse stellar population this averages out to within the systematic floor (the 0.100 mag default), but strictly it means intra-species comparisons (two [O III] measurements of the same object) are more rigorous than inter-species comparisons (an [O III] vs an [S II] measurement). The whitepaper is explicit about this limitation.