Guides
Find and repair artifacts
gspice.repair() detects unmasked spectral artifacts through pixelwise z-scores and a multi-pixel block scan, then replaces the corrupted pixels with conditional predictions.
Set up
Section titled “Set up”An artifact is a corruption of the data, such as a cosmic ray. The examples use three sets of simulated spectra of 400 pixels. Prepare your spectra explains why each set is separate.
import numpy as np
import gspice
rng = np.random.default_rng(4)train = gspice.simulate(2000, 400, rng=rng)clean = gspice.simulate(1000, 400, rng=rng)test = gspice.simulate(500, 400, rng=rng, artifacts=0.5)model, _ = gspice.fit(train.flux, train.ivar)trainholds the training spectra of the model.cleanholds clean spectra, which have no artifacts.testholds the spectra to repair. Half of them have one artifact each: a cosmic ray, a bad column or a shifted segment.
Block thresholds from clean spectra
Section titled “Block thresholds from clean spectra”An artifact that covers many pixels also corrupts the pixels that predict it, so a test
of each pixel can miss it. repair therefore also runs a
block scan. It compares each
block of consecutive pixels with its
prediction from the pixels beyond a guard window. Here
gspice.fit_blocks() sets a threshold for
each of five blocks of 150 pixels from the clean spectra:
model = gspice.fit_blocks(model, clean.flux, clean.ivar)print(model.blocks.starts, model.blocks.width)[ 0 75 150 225 250] 150Where to get clean spectra
Section titled “Where to get clean spectra”Training, threshold and held-out spectra
states which spectra are clean enough, and how many fit_blocks needs. If you have no
clean spectra, skip fit_blocks and read Without clean spectra.
Repair
Section titled “Repair”repair finds the artifacts of each spectrum and replaces them:
result = gspice.repair(model, test.flux, test.ivar)amplitude = np.abs(test.flux - test.truth) * np.sqrt(test.ivar)strong = test.artifacts & (amplitude > 5)found = result.replaced | result.ignoredother = ~test.artifactsprint(f"replaced: {result.replaced[strong].mean():.0%} of artifact pixels over 5σ")print(f"replaced or ignored: {found[strong].mean():.0%}")print(f"replaced: {result.replaced[other].mean():.2%} of the other pixels")replaced: 80% of artifact pixels over 5σreplaced or ignored: 96%replaced: 0.62% of the other pixelsamplitude is the change that the artifact made to a pixel, in units of its noise.
result is a gspice.Repaired, as from
infill. Each pixel that repair detects is a
flagged pixel, and it has one of two
fates:
- A replaced pixel, in
result.replaced, holds its prediction inresult.flux. - An ignored pixel, in
result.ignored, keeps its input value, and no prediction uses it.
result.replaced also holds every bad pixel of the input. result.blocks marks the
blocks that the scan flagged in each spectrum.
A clean spectrum can get a replaced pixel too:
none = ~test.artifacts.any(axis=1)flagged = result.replaced[none].any(axis=1)print(f"{flagged.mean():.1%} of the clean test spectra")0.4% of the clean test spectraThis is the fraction of the test spectra without an artifact that got a replaced pixel.
The explainer below steps through repair on two of the test spectra, and shows their
masks after each step.
Choose a step, or use Previous and Next. With a step button focused, the arrow keys, Home and End move between the steps.
The input. The thin line is the flux without noise.
- observed flux
- flux without noise
- prediction
- ±1σ
- ±2σ
- replaced pixel
- ignored pixel
- flagged block
The stepper shows rows 19 and 63 of test above, with its model and block thresholds: a bad column and a shifted segment, on 400 pixels. The masks of step 1 come from gspice.pixelwise, and those of steps 2 to 4 from gspice.repair.
Without clean spectra
Section titled “Without clean spectra”Without clean spectra, you cannot set block thresholds. Call repair with
blockwise=False: it then finds artifacts only by the z-score of each pixel. This
compares the two on the pixels of the shifted segments:
length = test.artifacts.sum(axis=1, keepdims=True)types = { "cosmic ray": length <= 3, "bad column": (length > 3) & (length <= 20), "shifted segment": length > 20,}shifted = test.artifacts & types["shifted segment"]plain = gspice.repair(model, test.flux, test.ivar, blockwise=False)print(f"with the block scan: {result.replaced[shifted].mean():.0%} replaced")print(f"without it: {plain.replaced[shifted].mean():.0%} replaced")with the block scan: 25% replacedwithout it: 21% replacedEach test spectrum has at most one artifact, and a shifted segment is the only one
longer than 20 pixels. The block scan replaces more of each segment. A model without
blocks needs blockwise=False; otherwise repair raises a ValueError.
Tuning
Section titled “Tuning”repair replaces a pixel whose exceeds nsigma_replace, 10 by default. It
ignores a pixel whose exceeds nsigma_ignore, 5 by default. A lower
nsigma_replace replaces more artifacts, and more good pixels:
for nsigma in (10, 8, 6): tuned = gspice.repair(model, test.flux, test.ivar, nsigma_replace=nsigma) print( f"nsigma_replace={nsigma:>2}: {tuned.replaced[strong].mean():.0%} of the " f"artifact pixels, {tuned.replaced[other].mean():.2%} of the others" )nsigma_replace=10: 80% of the artifact pixels, 0.62% of the othersnsigma_replace= 8: 89% of the artifact pixels, 0.67% of the othersnsigma_replace= 6: 95% of the artifact pixels, 0.72% of the othersChange one option at a time, and measure the result as above on spectra whose artifacts you know. The other options are:
| Option | Default | Effect |
|---|---|---|
dilate | 1 | Also flags this many pixels on each side of each flagged pixel |
nguard | 20 | The half-width of the guard window of the z-scores, in pixels |
runlength, halfwidth | 5, 150 | A run of at least runlength flagged pixels also ignores the halfwidth pixels on each side |
minfrac | 0.5 | The run rule applies only where it leaves this fraction of the pixels to predict from |
The procedure states each step of repair.
What it misses
Section titled “What it misses”repair finds an artifact only where its pixels disagree with their prediction. This
shows the result for each type of artifact:
for name, kind in types.items(): pixels = strong & kind print( f"{name:>15}: {result.replaced[pixels].mean():.0%} replaced, " f"{found[pixels].mean():.0%} replaced or ignored" ) cosmic ray: 89% replaced, 100% replaced or ignored bad column: 100% replaced, 100% replaced or ignoredshifted segment: 76% replaced, 96% replaced or ignoredHere a cosmic ray covers 1 to 3 pixels, a bad column 5 to 20, and a shifted segment 200 to 399. Three kinds of artifact stay in the output:
- Weak artifacts. A pixel with below
nsigma_ignoreis not flagged, unless it lies near a flagged pixel or in a block that the block scan flags. - Long shifted segments. Inside a segment, the pixels that predict each pixel are
shifted too.
repairreplaced 76% of their pixels over 5σ above, but 25% of all their pixels in Without clean spectra. Long shifted segments measures this limit. - Artifacts at the same pixels in every training spectrum. The model learns them as part of the spectra.
artifact
A corruption of the data, such as a cosmic ray, a bad column or a shifted segment.
gspice.repair() finds artifacts that no mask
marks.
clean spectrum
A spectrum without an artifact.
gspice.fit_blocks() sets its thresholds
from clean spectra.
shifted segment
An artifact in which a run of pixels has a shifted wavelength scale, so that its lines are recorded a few pixels off.
block scan
The comparison of each block of a spectrum with its prediction from the pixels beyond its guard window, by the statistic of Detection.
block
A run of width consecutive pixels that the block scan tests together, .
gspice.Blocks holds the first pixel of each
block and their thresholds.
flagged pixel
A pixel that gspice.repair() detects as an
artifact. It becomes a replaced or an ignored pixel.
replaced pixel
A pixel of the set , whose value an infill replaces by its conditional mean.
ignored pixel
A pixel in that is not replaced: it is not conditioned on, and keeps its input
value. repair reports its ignored pixels as .