Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Plume vetting — a spectral test for matched-filter candidates

Plume vetting

A matched filter scores each pixel by one projection of its spectrum onto methane’s target, so anything that removes light under methane’s 2.3 μm band scores: carbonate and clay soils, salt pans, dark water, shadows, striping. A segmentation network on the matched-filter map learns plume shapes, which a plume-shaped confounder also has. Plume vetting (Xiang et al. [1]; Růžička et al. [2]) goes back to the radiances behind a candidate. It pairs the candidate’s strongest pixels with pixels outside it that have the same surface, divides them, and asks whether the ratio has the shape of methane’s transmittance. EMIT’s daily methane digest alerts without review when the misfit score DnormD_{\text{norm}} is below 0.3.

The work splits along a package boundary, “radiative transfer does not know about plumes”. radtrax answers a question about spectra: given two sets of pixels, how similar is the ratio of their paired mean radiances to a gas’s transmittance? It returns the fitted column ΔX^\widehat{\Delta X} and DnormD_{\text{norm}}, and knows nothing of candidates. plumax answers the questions about plume objects: which of a candidate’s pixels are in (the seeds) and which are out (the background pool), how this scene’s clutter scores in the candidate’s shape (the null), and what to decide.

Vetting is a second test on the same data, not an independent one: its pixels are chosen by the matched filter and come from the same cube. It is more selective because it tests the shape of the absorption across a window of bands, where the matched filter tests one projection.

1. The test plumax runs

For in-pixels II and out-pixels OO of one radiance cube, radtrax pairs them one to one on the bands the gas does not absorb, keeps the best-matched half, and divides the pairs’ mean radiances into a ratio spectrum R(λ)≈η(λ)E(λ;ΔX)R(\lambda) \approx \eta(\lambda) E(\lambda; \Delta X), with η\eta the ratio of the paired surfaces and EE the gas’s band transmittance from radtrax’s forward model. It fits RR with a Legendre continuum times EE, profiling over a grid of ΔX\Delta X refined between its nodes, and scores the fit with Xiang et al.'s eq. 11, DnormD_{\text{norm}}: 0 for a perfect fit, about 1 when nothing gas-shaped is left. The derivations, the curve of growth and the reasons for each setting are on radtrax’s absorption-shape test page.

Everything below is how plumax chooses II and OO from a candidate, and what it does with the scores.

2. Seeds and the background pool

Write MM for the candidate’s pixels with a finite matched-filter score ΔX^MF\widehat{\Delta X}_{\text{MF}}, and qq for the score’s 99th percentile over MM. The seeds are the k=40k = 40 strongest pixels of MM at or below the cap, grown by one pixel inside MM (select_seeds):

I0=top⁡k{p∈M:ΔX^MF(p)≤q},I=grow⁡g(I0),grow⁡(A)=(A⊕3×3)∩M,I_0 = \operatorname{top}_k\{p \in M : \widehat{\Delta X}_{\text{MF}}(p) \le q\}, \qquad I = \operatorname{grow}^{g}(I_0), \quad \operatorname{grow}(A) = (A \oplus 3{\times}3) \cap M ,

with ⊕ 3×3\oplus\, 3{\times}3 one binary dilation by the 3 × 3 square and g=1g = 1. The background pool is every pixel within a box of bb pixels around the seeds’ bounding box, outside every candidate McM_c of the scene, with a small matched-filter score (background_pool):

O=(box⁡b(I)∖I∖⋃cMc)∩{p:∣ΔX^MF(p)∣<τbg},O = \Big( \operatorname{box}_b(I) \setminus I \setminus \textstyle\bigcup_c M_c \Big) \cap \{p : |\widehat{\Delta X}_{\text{MF}}(p)| < \tau_{\text{bg}}\} ,

with τbg\tau_{\text{bg}} the window’s mf_background_mol_m2 (30 ppm·m for EMIT’s CH₄ window) and b=200b = 200. So I⊆MI \subseteq M, O∩I=∅O \cap I = \emptyset and O∩Mc=∅O \cap M_c = \emptyset for every candidate. These are the reference package’s settings [3], pv/config/config.yaml; the two sets then go to radtrax’s pairing as flat pixel indices.

Why each setting

Seeds come from the matched filter. The depth of methane’s absorption in RR grows with ΔX\Delta X, so the strongest pixels give the deepest and best-resolved transmittance.

The seeds’ noise is not innocent. Choosing the kk largest scores selects pixels whose noise happens to point along methane’s target, so the seeds’ spectra are biased toward methane’s shape by the selection itself. This is one reason vetting is not independent of the matched filter. Growing the seeds by one pixel adds neighbours that were not chosen for their own noise, and it keeps the in-set a connected region rather than a scatter of isolated pixels.

The cap at the 99th percentile. The reference code does not say why it caps there. The cap drops the mask’s most extreme scores, which is where a single bad or saturated pixel would land.

Growth is one dilation. The reference code grows the seeds with a Gaussian filter of σ = 1 and keeps pixels above 0.05. On a binary mask that is exactly one 3 × 3 dilation, which plumax computes as a max-pool in JAX.

The background pool. A small matched-filter score keeps plume pixels out of LoutL_{\text{out}}, where they would cancel the signal being tested. The box keeps the pool near the seeds, so that each pair shares its sun, elevation and water vapour, which is what lets them cancel in the ratio. Every candidate of the scene is kept out, not only this one, so a second plume or confounder never serves as background.

3. A null from the same scene

The honest null is this scene’s clutter with no plume. Vet SS copies MsM_s of the mask, each rotated and translated to where there is no candidate (null_masks), exactly as the candidate was vetted, and report (null_pvalue)

p^=1S′∑s=1S′1[Dnorm(Ms)≤Dnorm(M)]\hat p = \frac{1}{S'} \sum_{s=1}^{S'} \mathbb{1}\left[ D_{\text{norm}}(M_s) \le D_{\text{norm}}(M) \right]

over the S′S' copies with a finite score. DnormD_{\text{norm}}'s threshold depends on the band set, the continuum’s degree and the ratio’s noise, so p^\hat p travels between sensors where 0.3 does not.

Each copy draws an angle θ∼U[0°,360°)\theta \sim U[0°, 360°) and rotates MM about the image centre (nearest neighbour); a rotation that keeps fewer than 90 % of the pixels is discarded. Among the integer shifts Δ\Delta that keep the rotated mask MθM_\theta inside the image, the free ones are those with

(Mθ+Δ)∩(M∪⋃cMc)=∅,(M_\theta + \Delta) \cap \Big( M \cup \textstyle\bigcup_c M_c \Big) = \emptyset ,

found exactly as the zeros of the correlation of the candidates with MθM_\theta’s bounding box; one free shift is drawn uniformly. Randomness comes only from the caller’s jax.random key, so the same key gives the same copies.

Reading p^\hat p

What it estimates. p^\hat p is the fraction of null copies that score at least as well as the candidate. A small p^\hat p says that a plume-free mask of the candidate’s shape, placed elsewhere in this scene, rarely fits methane this well.

Its resolution. With SS copies, p^\hat p moves in steps of 1/S1/S. p^=0\hat p = 0 says only that no copy did as well, not that the probability is zero. The usual Monte Carlo form, (1+S′p^)/(1+S′)(1 + S'\hat p) / (1 + S'), is never zero and is a valid p-value when the candidate and the copies are exchangeable under the null [4]. With S=100S = 100, candidate A’s p^=0.00\hat p = 0.00 is 1/101≈0.011/101 \approx 0.01 in that form. For a score where higher is better, such as a likelihood ratio, pass both scores negated.

What the null samples. Each copy runs the whole chain, seeds to DnormD_{\text{norm}}, so the selection of seeds by the matched filter happens inside the null too. The selection of the mask itself does not. Copies sit wherever there is no candidate, so they sample this scene’s background in the mask’s shape, not background that the matched filter flagged. A confounder is unusual background by construction, which is why the null calibrates the score but cannot replace the fit’s test of shape.

The copies are not independent. They overlap one another and draw on the same background pool, so the tail of the null is estimated less precisely than SS independent draws would estimate it.

4. One candidate

vet_candidate runs the steps in their fixed order with the window’s band selection: the seeds (1) and the pool (2), radtrax’s pairing on the window’s pairing bands, the ratio on its fitting bands, the fit and DnormD_{\text{norm}}. Its VettingResult carries ΔX^\widehat{\Delta X} in mol/m², DnormD_{\text{norm}}, the number of pairs, the ratio and the fit (continuum PP and transmittance EE, the spectra an analyst plots as R/PR/P against EE), and the continuum-only residual at ΔX=0\Delta X = 0 beside the fit’s residual at ΔX^\widehat{\Delta X}.

ΔX^\widehat{\Delta X} is the column of the mean transmittance over the pairs, not the mean column: EE is convex in ΔX\Delta X, so it sits below the mean of the truth over the seeds. It is a score, not a retrieval; the column for an emission rate comes from a per-pixel retrieval of the vetted plume.

5. One scene

The notebook builds a 64 × 64 EMIT-like scene: 30 bands outside methane’s band, 9 inside it (2296–2356 nm), three surface classes, noise at SNR 300. Candidate A is a methane plume; candidate B is a plume-shaped patch of a surface whose reflectance dips by 6 % at 2335 nm, as a carbonate’s does.

Matched filter, meanΔX^\widehat{\Delta X}DnormD_{\text{norm}}p^\hat p
A, plume593 ppm·m1,741 ppm·m0.140.00
B, carbonate1,162 ppm·m01.000.45

The matched filter scores B at twice A; vetting reverses the order. A’s column is 93 % of the truth over its kept pixels: about 3 points go to averaging transmittances, the rest to paired surfaces that do not match exactly. With a constant absorption coefficient A’s column would be 1,691 ppm·m. B’s p^\hat p was 0.43 when the null rejected random shifts; exact placement (4) draws other copies and gives 0.45 (A’s is 0 either way).

6. The decision

plumax.vetting returns scores, not verdicts. Three rules are on the table (#158): a fixed cut on DnormD_{\text{norm}} (EMIT’s 0.3), which depends on the sensor’s bands and noise; the scene null’s p^\hat p (3), which calibrates the score against this scene’s clutter; and a likelihood ratio between the fits with and without the gas, from VettingResult’s two residuals. Until #158 settles it, report p^\hat p with every score.

7. What plumax computes

StepEquationFunctionPackageIssue
Seeds(1)select_seedsplumax#154
Background pool(2)background_poolplumax#154
A gas’s fitting and pairing bandsAbsorptionWindowradtraxradtrax#155
Transmittance on a grid of ΔX\Delta Xtransmittance_tableradtraxradtrax#154
One-to-one pairs and the ratiopair_pixels, ratio_spectrumradtraxradtrax#149
Profile fit and scorefit_transmittance, TransmittanceFit, d_normradtraxradtrax#150
One candidatevet_candidate, VettingResultplumax#156
The scene null(4), (3)null_masks, null_pvalueplumax#155

The radtrax names are in radtrax.retrieval.absorption (API, theory). plumax.vetting is an L1 module on arrays; the operator on carriers, plumax.geo.PlumeVetting, follows (#157).

8. Where plumax departs from the reference code

Footnotes
  1. Xiang, C., et al. (2024). A novel spectroscopic method for vetting methane point sources. AGU Fall Meeting, abstract A21K-1903.

  2. Růžička, V., Thompson, D. R., Fahlen, J. E., et al. (2026). Fully automatic trace gas plume detection. arXiv:2605.03372.

  3. EMIT Science Data System (2025). plume-vetting, revision 47fc6ad. https://github.com/emit-sds/plume-vetting (Apache-2.0).

  4. Phipson, B. & Smyth, G. K. (2010). Permutation p-values should never be zero: calculating exact p-values when permutations are randomly drawn. Statistical Applications in Genetics and Molecular Biology, 9(1), Article 39.