Skip to content

Commit 293b56d

Browse files
committed
Fix peak-fits where one or more channels had a small, but non-zero number of counts.
This occurs primarily on background-subtracted spectra, that may have channel contents like 0.0007, which even a single channel would mess up the whole fit. Changed so that any channel contents under 1.0, will have uncertainty 1.0. Not totally happy with this, but hopefully does avoid this particular edge case.
1 parent 946af22 commit 293b56d

File tree

2 files changed

+48
-26
lines changed

2 files changed

+48
-26
lines changed

src/PeakFit.cpp

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,15 @@ using namespace std;
7676

7777
using SpecUtils::Measurement;
7878

79+
// 20240911: The minimum uncertainty allowed for a gamma spectrum channel.
80+
// Background subtracted spectra can end up with tiny bins, like 0.0007,
81+
// which if we take its uncertainty to be its sqrt, a single bin like this will
82+
// totally mess up the whole ROI. So we'll impose a minimum uncertainty on
83+
// each channel.
84+
// However, if a spectrum is scaled, and not Poisson errored, this will totally
85+
// mess things up (even though it wouldnt be right in the first place).
86+
#define MIN_CHANNEL_UNCERT 1.0
87+
7988
template<class T>
8089
bool matrix_invert( const boost::numeric::ublas::matrix<T>& input,
8190
boost::numeric::ublas::matrix<T> &inverse )
@@ -1774,7 +1783,7 @@ double chi2_for_region( const PeakShrdVec &peaks,
17741783
const double ncontinuum = continuum->offset_integral(xbinlow, xbinup, data);
17751784

17761785
const double npeak = gauss_counts[i];
1777-
const double uncert = ndata > 0.0 ? sqrt(ndata) : 1.0;
1786+
const double uncert = ndata > MIN_CHANNEL_UNCERT ? sqrt(ndata) : 1.0;
17781787
const double chi = (ndata-ncontinuum-npeak)/uncert;
17791788

17801789
chi2 += chi*chi;
@@ -1803,7 +1812,7 @@ double chi2_for_region( const PeakShrdVec &peaks,
18031812
{
18041813
const double peakarea = i->second;
18051814
const double ndata = data->gamma_channel_content( i->first );
1806-
const double uncert = ndata > 0.0 ? sqrt(ndata) : 1.0;
1815+
const double uncert = ndata > MIN_CHANNEL_UNCERT ? sqrt(ndata) : 1.0;
18071816
const double chi = (ndata-peakarea)/uncert;
18081817
chi2 += chi*chi;
18091818
}//for( int bin = minbin; bin <= maxbin; ++bin )
@@ -2069,7 +2078,7 @@ double evaluate_chi2dof_for_range( const std::vector<PeakDef> &peaks,
20692078
if( y_pred < 0.0 )
20702079
y_pred = 0.0;
20712080

2072-
const double uncert = (y > 0.0f ? sqrt(y) : 1.0);
2081+
const double uncert = (y > MIN_CHANNEL_UNCERT ? sqrt(y) : 1.0);
20732082
chi2 += std::pow( (y_pred - y) / uncert, 2.0 );
20742083
}//for( int bin = 0; bin < nbin; ++bin )
20752084

@@ -3702,7 +3711,7 @@ bool check_lowres_single_peak_fit( const std::shared_ptr<const PeakDef> peak,
37023711
const double x = dataH->gamma_channel_lower( channel );
37033712
const double y = dataH->gamma_channel_content( channel );
37043713
const double y_pred = poly_coeffs[0] + poly_coeffs[1]*x;
3705-
const double uncert = (y > 0.0f ? std::sqrt(y) : 1.0);
3714+
const double uncert = (y > MIN_CHANNEL_UNCERT ? std::sqrt(y) : 1.0);
37063715
const double thichi2 = std::pow( (y_pred - y) / uncert, 2.0 );
37073716

37083717
linechi2 += thichi2;
@@ -4101,7 +4110,7 @@ PeakRejectionStatus check_lowres_multi_peak_fit( const vector<std::shared_ptr<co
41014110
const double x = dataH->gamma_channel_lower( channel );
41024111
const double y = dataH->gamma_channel_content( channel );
41034112
const double y_pred = poly_coeffs[0] + poly_coeffs[1]*x;
4104-
const double uncert = (y > 0.0f ? sqrt(y) : 1.0);
4113+
const double uncert = (y > MIN_CHANNEL_UNCERT ? sqrt(y) : 1.0);
41054114
linechi2 += std::pow( (y_pred - y) / uncert, 2.0 );
41064115
}//for( int bin = 0; bin < nbin; ++bin )
41074116

@@ -4624,7 +4633,7 @@ bool check_highres_single_peak_fit( const std::shared_ptr<const PeakDef> peak,
46244633
const double x = dataH->gamma_channel_lower( channel );
46254634
const double y = dataH->gamma_channel_content( channel );
46264635
const double y_pred = poly_coeffs[0] + poly_coeffs[1]*x;
4627-
const double uncert = (y > 0.0f ? std::sqrt(y) : 1.0);
4636+
const double uncert = (y > MIN_CHANNEL_UNCERT ? std::sqrt(y) : 1.0);
46284637
const double thichi2 = std::pow( (y_pred - y) / uncert, 2.0 );
46294638

46304639
linechi2 += thichi2;
@@ -6262,7 +6271,7 @@ double fit_to_polynomial( const float *x, const float *data, const size_t nbin,
62626271

62636272
for( size_t row = 0; row < nbin; ++row )
62646273
{
6265-
const double uncert = (data[row] > 0.0 ? sqrt( data[row] ) : 1.0);
6274+
const double uncert = (data[row] > MIN_CHANNEL_UNCERT ? sqrt( data[row] ) : 1.0);
62666275
b(row) = (data[row] > 0.0 ? sqrt( data[row] ) : 0.0);
62676276
for( int col = 0; col < poly_terms; ++col )
62686277
A(row,col) = std::pow( double(x[row]), double(col)) / uncert;
@@ -6292,7 +6301,7 @@ double fit_to_polynomial( const float *x, const float *data, const size_t nbin,
62926301
double y_pred = 0.0;
62936302
for( int i = 0; i < poly_terms; ++i )
62946303
y_pred += a(i) * std::pow( double(x[bin]), double(i) );
6295-
const double uncert = (data[bin] > 0.0 ? sqrt( data[bin] ) : 1.0);
6304+
const double uncert = (data[bin] > MIN_CHANNEL_UNCERT ? sqrt( data[bin] ) : 1.0);
62966305
chi2 += std::pow( (y_pred - data[bin]) / uncert, 2.0 );
62976306
}//for( int bin = 0; bin < nbin; ++bin )
62986307

@@ -6375,16 +6384,15 @@ double fit_amp_and_offset( const float *x, const float *data, const size_t nbin,
63756384
// cerr << "{" << means[i] << ", " << sigmas[i] << "}, ";
63766385
// cerr << endl << endl;
63776386

6378-
double step_roi_data_sum = 0.0, step_cumulative_data = 0.0;
6387+
double roi_data_sum = 0.0, step_cumulative_data = 0.0;
63796388

63806389
const double roi_lower = x[0];
63816390
const double roi_upper = x[nbin];
6382-
if( step_continuum )
6383-
{
6384-
for( size_t row = 0; row < nbin; ++row )
6385-
step_roi_data_sum += data[row];
6386-
}
63876391

6392+
for( size_t row = 0; row < nbin; ++row )
6393+
roi_data_sum += std::max( data[row], 0.0f );
6394+
6395+
const double avrg_data_val = roi_data_sum / nbin;
63886396

63896397
// For the RelACtAuto calcs, there can easily be 100 peaks, so for this case we'll calculate
63906398
// their contribution multithreaded.
@@ -6447,7 +6455,14 @@ double fit_amp_and_offset( const float *x, const float *data, const size_t nbin,
64476455
const double x1_rel = x1 - ref_energy;
64486456

64496457
//const double uncert = (dataval > 0.0 ? sqrt(dataval) : 1.0);
6450-
double uncert = (dataval > 0.0 ? sqrt(dataval) : 1.0);
6458+
// If we are background subtracting a spectrum, we can end up with bins with really
6459+
// small values, like 0.0007, which, even one of would mess the whole fit up if
6460+
// we take its uncertainty to be its square-root, so in this case we will, fairly arbitrarily
6461+
// we want to use an uncert of 1.
6462+
// However, there are also highly scaled spectra, whose all values are really small, so
6463+
// in this case we want to do something more reasonable.
6464+
// TODO: evaluate these choices of thresholds and tradeoffs, more better
6465+
double uncert = (dataval > MIN_CHANNEL_UNCERT ? sqrt(dataval) : 1.0);
64516466

64526467
if( step_continuum )
64536468
step_cumulative_data += dataval;
@@ -6511,13 +6526,13 @@ double fit_amp_and_offset( const float *x, const float *data, const size_t nbin,
65116526
{
65126527
// This logic mirrors that of PeakContinuum::offset_integral(...), and code
65136528
// If you change it in one place - change it in here, below, and in offset_integral.
6514-
const double frac_data = (step_cumulative_data - 0.5*data[row]) / step_roi_data_sum;
6529+
const double frac_data = (step_cumulative_data - 0.5*data[row]) / roi_data_sum;
65156530
const double contribution = frac_data * (x1 - x0);
65166531

65176532
A(row,col) = contribution / uncert;
65186533
}else if( step_continuum && (num_polynomial_terms == 4) )
65196534
{
6520-
const double frac_data = (step_cumulative_data - 0.5*data[row]) / step_roi_data_sum;
6535+
const double frac_data = (step_cumulative_data - 0.5*data[row]) / roi_data_sum;
65216536

65226537
double contrib = 0.0;
65236538
switch( col )
@@ -6577,7 +6592,7 @@ double fit_amp_and_offset( const float *x, const float *data, const size_t nbin,
65776592
for( size_t channel = 0; channel < nbin; ++channel )
65786593
{
65796594
const double dataval = data[channel];
6580-
const double uncert = (dataval > 0.0 ? sqrt(dataval) : 1.0);
6595+
const double uncert = (dataval > MIN_CHANNEL_UNCERT ? sqrt(dataval) : 1.0);
65816596
A(channel,npoly + i) = peak_areas[channel] / uncert;
65826597
}//for( size_t channel = 0; channel < nbin; ++channel )
65836598
}//for( size_t i = 0; i < npeaks; ++i )
@@ -6733,7 +6748,7 @@ double fit_amp_and_offset( const float *x, const float *data, const size_t nbin,
67336748
{
67346749
// This logic mirrors that of PeakContinuum::offset_integral(...) and above code in this
67356750
// function that defines the matrix, see above for comments
6736-
const double frac_data = (step_cumulative_data - 0.5*data[bin]) / step_roi_data_sum;
6751+
const double frac_data = (step_cumulative_data - 0.5*data[bin]) / roi_data_sum;
67376752
const double contribution = frac_data * (x1 - x0);
67386753

67396754
y_pred += a(col)*contribution;
@@ -6742,7 +6757,7 @@ double fit_amp_and_offset( const float *x, const float *data, const size_t nbin,
67426757
// This logic mirrors that of PeakContinuum::offset_integral(...) and above code in this
67436758
// function that defines the matrix, see above for comments
67446759

6745-
const double frac_data = (step_cumulative_data - 0.5*data[bin]) / step_roi_data_sum;
6760+
const double frac_data = (step_cumulative_data - 0.5*data[bin]) / roi_data_sum;
67466761

67476762
double contrib = 0.0;
67486763
switch( col )
@@ -6774,7 +6789,7 @@ double fit_amp_and_offset( const float *x, const float *data, const size_t nbin,
67746789
y_pred += fixedAmpPeaks[i].gauss_integral( x0, x1 );
67756790

67766791
// cerr << "bin " << bin << " predicted " << y_pred << " data=" << data[bin] << endl;
6777-
const double uncert = (data[bin] > 0.0 ? sqrt( data[bin] ) : 1.0);
6792+
const double uncert = (data[bin] > MIN_CHANNEL_UNCERT ? sqrt( data[bin] ) : 1.0);
67786793
chi2 += std::pow( (y_pred - data[bin]) / uncert, 2.0 );
67796794
}//for( int bin = 0; bin < nbin; ++bin )
67806795

@@ -8412,7 +8427,7 @@ std::vector<PeakDef> search_for_peaks( const std::shared_ptr<const Measurement>
84128427
y_pred += continuum_coeffs[col] * (1.0/exp) * (pow(x1cont,exp) - pow(x0cont,exp));
84138428
}//for( int order = 0; order < maxorder; ++order )
84148429

8415-
const double uncert = (data[bin] > 0.0 ? sqrt( data[bin] ) : 1.0);
8430+
const double uncert = (data[bin] > MIN_CHANNEL_UNCERT ? sqrt( data[bin] ) : 1.0);
84168431

84178432
if( y_pred < 0.0 )
84188433
y_pred = 0.0;

src/PeakFitChi2Fcn.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -552,10 +552,17 @@ double PeakFitChi2Fcn::chi2( const double *params ) const
552552
const double nfitpeak = peak_counts[i];
553553
const double ncontinuum = continuum->offset_integral(energies[i], energies[i+1], m_data);
554554

555-
if( ndata > 0.000001 )
556-
chi2 += pow( (ndata - ncontinuum - nfitpeak), 2.0 ) / ndata;
557-
else
558-
chi2 += fabs(nfitpeak + ncontinuum); //This is a bit ad-hoc - is there a better solution? //XXX untested
555+
const double uncert2 = ndata > 1.0 ? ndata : 1.0;
556+
557+
// 20240911: Changed to prevent edge-case of background subtracted spectra having bin
558+
// contents like 0.0007, which then one channel of would ruin whole peak fit
559+
chi2 += pow( (ndata - ncontinuum - nfitpeak), 2.0 ) / uncert2;
560+
// Previous to 20240911, we used
561+
// if( ndata > 0.000001 )
562+
// chi2 += pow( (ndata - ncontinuum - nfitpeak), 2.0 ) / ndata;
563+
// else
564+
// chi2 += fabs(nfitpeak + ncontinuum); //This is a bit ad-hoc - is there a better solution? //XXX untested
565+
559566
}
560567
}else
561568
{

0 commit comments

Comments
 (0)