1.3. Spectroscopy Troubleshooting

The most common spectroscopy problems trace back to one thing: Curie fits the peaks it expects from the assigned isotopes and the current calibration, rather than searching the spectrum for unknown peaks. When the expectation is wrong — usually the energy calibration — peaks are missed or misfit. This page collects the failure modes we see most often, with their symptoms and fixes.

Whatever the symptom, check the fit’s output first: the fit_peaks summary line accounts for every candidate line (fit, or dropped with the responsible filter named), and sp.diagnostics records each attempted fit with flags for the problematic ones (see Spectroscopy How-to Guide). Most of the entries below appear as a line item in one or the other.

No peaks are fit, and the spectrum looks uncalibrated

Symptom: sp.peaks comes back empty (or nearly so) even though the spectrum plainly shows peaks, and known lines are nowhere near their energies in sp.plot().

Cause: the energy calibration stored in the spectrum file is wrong or missing. Curie predicts the channel of every gamma line from the energy calibration and evaluates its expected signal-to-noise ratio at that channel; if the calibration points at featureless background, every line fails the SNR_min test and is dropped without an error.

Fix: plot the raw histogram against channel number and identify one or two known lines by eye:

sp.plot(fit=False, xcalib=False)

then anchor the calibration with (channel, energy) pairs — here using the 121.8 keV line of 152Eu seen at channel 664:

sp.isotopes = ['152EU']
sp.auto_calibrate(peaks=[[664, 121.8]])

or set it directly if the coefficients are known:

sp.cb.engcal = [0.3, 0.184]
sp.fit_peaks()

Peaks fit correctly — until a saved calibration is applied

Symptom: the spectrum fits well on its own, but after assigning a saved calibration (sp.cb = 'eu_calib.json') the peaks shift or disappear. This one is trickier, because applying the calibration is exactly what you are supposed to do.

Cause: assigning a calibration replaces all three calibrations — energy, resolution and efficiency. The efficiency calibration is usually what you want from the file (it is a property of the detector and counting geometry), but the energy calibration it carries describes the electronics on the day the calibration was measured. Drift in the amplifier gain since then (or an intervening, forgotten recalibration of the ADC) means the file’s energy calibration no longer matches this spectrum, while the spectrum’s own header calibration was correct all along.

Fix: apply the saved calibration for its efficiency, but keep the spectrum’s own energy calibration:

sp = ci.Spectrum('sample_7cm.Spe')
engcal = sp.cb.engcal          # calibration from the file header
sp.cb = 'eu_calib.json'        # detector efficiency (overwrites engcal)
sp.cb.engcal = engcal          # restore this spectrum's energy calibration
sp.fit_peaks()

If the header calibration is also imperfect, follow with sp.auto_calibrate(), which makes small corrections using the assigned isotopes’ lines.

An expected peak is missing from the results

Symptom: a line you can see in the spectrum — and that you know the isotope emits — does not appear in sp.peaks. Everything else fits fine.

Cause: one of the peak-selection filters excluded it before fitting. The exclusion is reported: the summary line counts the dropped lines per filter, and ci.set_log_level('DEBUG') names each one individually:

[INFO] Spectrum(sample.Spe).fit_peaks: fit 12 peaks in 9 multiplets from
1 isotopes; dropped 31 candidates (2 SNR<4.0, 29 intensity<0.05%)

The defaults are chosen for typical activation spectra and will drop some legitimate lines:

  • E_min (default 75 keV) excludes all lower-energy lines — the 59.5 keV line of 241Am, for example, never fits at the default.

  • I_min (default 0.05%) excludes weak branches.

  • dE_511 excludes lines within a few keV of the 511 keV annihilation peak.

  • SNR_min excludes lines predicted to be too weak to fit — and the prediction uses the current efficiency calibration, so a badly wrong efficiency curve can veto a clearly visible peak.

  • X-rays are excluded entirely unless xrays=True.

Fix: relax the relevant filter:

sp.fit_peaks(E_min=40.0, I_min=0.01)   # admit lower-energy / weaker gammas
sp.fit_peaks(xrays=True, E_min=20.0)   # or: also include x-ray lines
sp.fit_peaks(SNR_min=2.0)              # or: admit marginal peaks

Each line is an independent alternative, not a sequence. If a clearly visible peak is being vetoed by SNR_min, the real problem is usually an efficiency calibration that badly underpredicts the peak — recalibrate (see the Spectroscopy Worked Examples) rather than lowering the threshold. If a line is missing because the isotope’s decay data doesn’t include it, add it manually with the gammas argument (see Spectroscopy How-to Guide).

Fits succeed, but they are bad fits

Symptom: fitted curves that visibly miss the data, large chi2 values in the peak table, or peak fit failed warnings for particular multiplets (groups of overlapping peaks that are fit together). Failed multiplets are crosshatched in red on sp.plot(), and every high-chi2 or at-bound fit is flagged in sp.diagnostics.

Cause and fix, by situation:

  • The peak sits on structure — the peak sits on a broad spectral feature rather than flat background: a backscatter peak or Compton edge (both produced by scattered gamma rays), or the shoulder of a much larger neighboring peak. The default SNIP background assumes a smoothly varying continuum and will not follow sharp features. Refit with a polynomial background, which is fit jointly with the peaks: sp.fit_peaks(bg='quadratic').

  • The fit window is too wide or too crowded. Wide windows (pk_width, default 7.5 peak widths) can pull neighboring structure into the fit, and crowded regions can exceed the multiplet limit. Reduce pk_width, or raise multi_max so overlapping peaks are fit together rather than truncated.

  • The peak shape doesn’t match — strong low-energy tailing on intense peaks (degraded detectors, high count rates) that the default fixed skew parameters underestimate. Let the skew be fit per peak with skew_fit=True (and step_fit=True for a per-peak step), at the cost of more free parameters per peak.

  • The starting point is too constrained. Amplitudes, centroids and widths are bounded around their predicted values; if the prediction is poor (e.g. a marginal energy calibration), loosen the bounds with the multipliers A_bound, mu_bound and sig_bound.

A chi2 well above one on a very high-statistics peak — values of ten or more are common on peaks with hundreds of thousands of counts, like the 122 keV peak in the Spectroscopy Worked Examples — is not by itself a bad fit: with that many counts, even percent-level imperfections of the peak model are statistically resolvable. Curie accounts for this by inflating the fit uncertainties by \(\sqrt{\chi^2_\nu}\) (see Gamma-ray Peak Fitting).