diff --git a/CMakeLists.txt b/CMakeLists.txt index ec851a95..e12e3932 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -322,7 +322,6 @@ set(sources src/D3SpectrumDisplayDiv.cpp external_libs/SpecUtils/d3_resources/SpectrumChartD3.js external_libs/SpecUtils/d3_resources/SpectrumChartD3.css - external_libs/SpecUtils/d3_resources/SpectrumChartD3StandAlone.css src/D3TimeChart.cpp src/ZipArchive.cpp src/MultimediaDisplay.cpp @@ -570,10 +569,7 @@ deploy_js_resource("${CMAKE_CURRENT_SOURCE_DIR}/external_libs/SpecUtils/d3_resou ) -set(CSS_RESOURCES_TO_COPY - "SpectrumChartD3.css" - "SpectrumChartD3StandAlone.css" -) +set( CSS_RESOURCES_TO_COPY "SpectrumChartD3.css" ) foreach(_file ${CSS_RESOURCES_TO_COPY}) deploy_css_resource("${CMAKE_CURRENT_SOURCE_DIR}/external_libs/SpecUtils/d3_resources/${_file}" diff --git a/InterSpec/D3SpectrumDisplayDiv.h b/InterSpec/D3SpectrumDisplayDiv.h index 80f74241..4b6f2aac 100644 --- a/InterSpec/D3SpectrumDisplayDiv.h +++ b/InterSpec/D3SpectrumDisplayDiv.h @@ -476,6 +476,7 @@ class D3SpectrumDisplayDiv : public Wt::WContainerWidget std::unique_ptr > m_legendClosedJS; std::unique_ptr > m_sliderDisplayed; + std::unique_ptr > m_yAxisTypeChanged; // Wt Signals //for all the bellow, the doubles are all the coordinated of the action @@ -535,6 +536,9 @@ class D3SpectrumDisplayDiv : public Wt::WContainerWidget /** Called when user shows or closes the slider chart, by using the SVG buttons */ void sliderChartDisplayedCallback( const bool madeVisisble ); + /** Called when the user double-clicks on y-axis title, which toggles between linear and log y-axis */ + void yAxisTypeChangedCallback( const std::string &type ); + /** The javascript variable name used to refer to the SpecrtumChartD3 object. Currently is `jsRef() + ".chart"`. */ diff --git a/InterSpec/DetectionLimitSimple.h b/InterSpec/DetectionLimitSimple.h index a6802a20..bf73490b 100644 --- a/InterSpec/DetectionLimitSimple.h +++ b/InterSpec/DetectionLimitSimple.h @@ -43,6 +43,7 @@ namespace Wt { class WMenu; class WText; + class WCheckBox; class WComboBox; class WLineEdit; class WTabWidget; @@ -168,6 +169,7 @@ class DetectionLimitSimple : public Wt::WContainerWidget void handleUserChangedFwhm(); void handleDeconPriorChange(); + void handleNoSignalPresentChanged(); void handleDeconContinuumTypeChange(); void updateSpectrumDecorationsAndResultText(); @@ -227,7 +229,18 @@ class DetectionLimitSimple : public Wt::WContainerWidget Wt::WLineEdit *m_distance; - enum ConfidenceLevel { OneSigma, TwoSigma, ThreeSigma, FourSigma, FiveSigma, NumConfidenceLevel }; + enum ConfidenceLevel + { + NinetyFivePercent, //0.95 + NinetyNinePercent, //0.99 + OneSigma, //0.682689492137086 + TwoSigma, //0.954499736103642 + ThreeSigma, //0.997300203936740 + FourSigma, //0.999936657516334 + FiveSigma, //0.999999426696856 + NumConfidenceLevel + };//enum ConfidenceLevel + Wt::WComboBox *m_confidenceLevel; DetectorDisplay *m_detectorDisplay; @@ -254,6 +267,8 @@ class DetectionLimitSimple : public Wt::WContainerWidget Wt::WPushButton *m_addFwhmBtn; Wt::WPushButton *m_selectDetectorBtn; + WContainerWidget *m_isBackgroundDiv; + Wt::WCheckBox *m_isBackgroundSpectrum; Wt::WLabel *m_continuumPriorLabel; Wt::WComboBox *m_continuumPrior; Wt::WLabel *m_continuumTypeLabel; diff --git a/InterSpec/HelpSystem.h b/InterSpec/HelpSystem.h index 51071c12..bf2d69f4 100644 --- a/InterSpec/HelpSystem.h +++ b/InterSpec/HelpSystem.h @@ -98,7 +98,8 @@ namespace HelpSystem enum class ToolTipPrefOverride { AlwaysShow, - RespectPreference + RespectPreference, + InstantAlways }; enum class ToolTipPosition diff --git a/InterSpec_resources/DetectionLimitSimple.css b/InterSpec_resources/DetectionLimitSimple.css index 9932072d..43b3ebda 100644 --- a/InterSpec_resources/DetectionLimitSimple.css +++ b/InterSpec_resources/DetectionLimitSimple.css @@ -112,6 +112,14 @@ } +.DetectionLimitSimple .BackCbDiv { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-end; + column-gap: 5px; +} + .DeconvMoreInfo { display: flex; flex-direction: column; @@ -138,3 +146,4 @@ border-collapse: separate; border-spacing: 5px 5px; } + diff --git a/InterSpec_resources/GridLayoutHelpers.css b/InterSpec_resources/GridLayoutHelpers.css index 2f66e72a..e04ba988 100644 --- a/InterSpec_resources/GridLayoutHelpers.css +++ b/InterSpec_resources/GridLayoutHelpers.css @@ -13,7 +13,7 @@ justify-self: stretch; } -.GridJustifyCenter +.GridJustifyCenter, .GridHorCenter { justify-self: center; } diff --git a/InterSpec_resources/app_text/DetectionLimitSimple.xml b/InterSpec_resources/app_text/DetectionLimitSimple.xml index 99d3fbdf..b291d56c 100644 --- a/InterSpec_resources/app_text/DetectionLimitSimple.xml +++ b/InterSpec_resources/app_text/DetectionLimitSimple.xml @@ -10,10 +10,18 @@ ROI Upper: Num Side Chan.: FWHM: - Prior: - Unknown/Present - Not Present - Cont. from sides + Cont. Norm: + Background spectrum + + Checking this option asserts that there is no signal present in this spectrum. +

+ The peak-region itself will be used to estimate the expected background. + The option to choose side-channels adjacent to the peak-region becomes no-longer relevant, so will be hidden. +

+
+ Floating + As no signal + From sides Changing the ROI excluded primary gamma - not changing Detector FWHM: {1} keV Rough Est. FWHM: {1} keV diff --git a/external_libs/SpecUtils b/external_libs/SpecUtils index 074524d9..b2b7a174 160000 --- a/external_libs/SpecUtils +++ b/external_libs/SpecUtils @@ -1 +1 @@ -Subproject commit 074524d9371f4b73765f6982418827c36ecb03a1 +Subproject commit b2b7a174135c712bd5ffac7d5453dff6d1fa7a1e diff --git a/src/ColorThemeWidget.cpp b/src/ColorThemeWidget.cpp index b2b9b90d..010a51be 100644 --- a/src/ColorThemeWidget.cpp +++ b/src/ColorThemeWidget.cpp @@ -215,7 +215,7 @@ ColorThemeWidget::ColorThemeWidget(WContainerWidget *parent) ++row; cell = table->elementAt(row, 0); cell->addStyleClass( "CTRowLabel" ); - new WLabel("Spectrum Chart Background", cell); + new WLabel("Spectrum Area", cell); cell = table->elementAt(row, 1); if( nativeColorSelect ) @@ -232,7 +232,7 @@ ColorThemeWidget::ColorThemeWidget(WContainerWidget *parent) ++row; cell = table->elementAt(row, 0); cell->addStyleClass( "CTRowLabel" ); - new WLabel("Spectrum Chart Margins", cell); + new WLabel("Spectrum Chart Bckgrnd", cell); cell = table->elementAt(row, 1); cell->addStyleClass( "CTSelect" ); if( nativeColorSelect ) @@ -244,7 +244,7 @@ ColorThemeWidget::ColorThemeWidget(WContainerWidget *parent) cell = table->elementAt(row, 2); cell->addStyleClass( "CTRowDesc" ); new WText("Color for spectrum chart margins", cell); - m_specMarginSameAsBackground = new WCheckBox("Same as background", cell); + m_specMarginSameAsBackground = new WCheckBox("Same as spectrum area", cell); m_specMarginSameAsBackground->setInline(false); diff --git a/src/D3SpectrumDisplayDiv.cpp b/src/D3SpectrumDisplayDiv.cpp index e8fa81d0..86aef108 100644 --- a/src/D3SpectrumDisplayDiv.cpp +++ b/src/D3SpectrumDisplayDiv.cpp @@ -258,10 +258,9 @@ D3SpectrumDisplayDiv::D3SpectrumDisplayDiv( WContainerWidget *parent ) // Cancel right-click events for the div, we handle it all in JS setAttributeValue( "oncontextmenu", - "event.cancelBubble = true; event.returnValue = false; return false;" - ); + "event.cancelBubble = true; event.returnValue = false; return false;" ); - InterSpec::instance()->useMessageResourceBundle( "D3SpectrumDisplayDiv" ); + InterSpec::instance()->useMessageResourceBundle( "D3SpectrumDisplayDiv" ); //For development it may be useful to directly use the original JS/CSS files, // but normally we should use the resources CMake will copy into @@ -434,11 +433,15 @@ void D3SpectrumDisplayDiv::defineJavaScript() m_legendEnabled = false; m_legendDisabledSignal.emit(); }) ); + + + m_sliderDisplayed.reset( new Wt::JSignal(this, "sliderChartDisplayed") ); + m_sliderDisplayed->connect( boost::bind( &D3SpectrumDisplayDiv::sliderChartDisplayedCallback, this, boost::placeholders::_1 ) ); + + m_yAxisTypeChanged.reset( new Wt::JSignal(this, "yAxisTypeChanged") ); + m_yAxisTypeChanged->connect( boost::bind( &D3SpectrumDisplayDiv::yAxisTypeChangedCallback, this, boost::placeholders::_1 ) ); }//if( !m_xRangeChangedJS ) - m_sliderDisplayed.reset( new Wt::JSignal(this, "sliderChartDisplayed") ); - m_sliderDisplayed->connect( boost::bind( &D3SpectrumDisplayDiv::sliderChartDisplayedCallback, this, boost::placeholders::_1 ) ); - for( const string &js : m_pendingJs ) doJavaScript( js ); m_pendingJs.clear(); @@ -452,9 +455,8 @@ void D3SpectrumDisplayDiv::defineJavaScript() void D3SpectrumDisplayDiv::initChangeableCssRules() { WCssStyleSheet &style = wApp->styleSheet(); - - m_cssRules["GridColor"] = style.addRule( ".xgrid > .tick, .ygrid > .tick", "stroke: #b3b3b3" ); - m_cssRules["MinorGridColor"] = style.addRule( ".minorgrid", "stroke: #e6e6e6" ); + m_cssRules["GridColor"] = style.addRule( ":root", "--d3spec-grid-major-color: #b3b3b3;" ); //Not actually needed, as CSS will default to this + m_cssRules["MinorGridColor"] = style.addRule( ":root", "--d3spec-grid-minor-color: #e6e6e6;" ); //Not actually needed, as CSS will default to this }//void initChangeableCssRules() @@ -867,6 +869,9 @@ bool D3SpectrumDisplayDiv::yAxisIsLog() const void D3SpectrumDisplayDiv::setYAxisLog( bool log ) { + if( m_yAxisIsLog == log ) + return; + m_yAxisIsLog = log; if( isRendered() ) doJavaScript( m_jsgraph + (log ? ".setLogY();" : ".setLinearY();") ); @@ -1658,14 +1663,9 @@ void D3SpectrumDisplayDiv::setAxisLineColor( const Wt::WColor &color ) if( m_cssRules.count(rulename) ) style.removeRule( m_cssRules[rulename] ); - m_cssRules[rulename] = style.addRule( ":root", "--d3spec-axis-color: " + m_axisColor.cssText() ); - + // Sets axis colors, and ".peakLine, .escapeLineForward, .mouseLine, .secondaryMouseLine" - //ToDo: is setting feature line colors okay like this - rulename = "FeatureLinesColor"; - if( m_cssRules.count(rulename) ) - style.removeRule( m_cssRules[rulename] ); - m_cssRules[rulename] = style.addRule( ".peakLine, .escapeLineForward, .mouseLine, .secondaryMouseLine", "stroke: " + m_axisColor.cssText() ); + m_cssRules[rulename] = style.addRule( ":root", "--d3spec-axis-color: " + m_axisColor.cssText() ); //ToDo: figure out how to make grid lines look okay. //rulename = "GridColor"; @@ -1701,8 +1701,9 @@ void D3SpectrumDisplayDiv::setChartMarginColor( const Wt::WColor &color ) if( m_cssRules.count(rulename) ) style.removeRule( m_cssRules[rulename] ); + m_cssRules[rulename] = style.addRule( ":root", "--d3spec-background-color: " + color.cssText() + ";" ); //Actually this will set the background for the entire chart... - m_cssRules[rulename] = style.addRule( "#" + id() + " > svg", "background: " + color.cssText() ); + //m_cssRules[rulename] = style.addRule( "#" + id() + " > svg", "background: " + color.cssText() ); // to set background color for just this chart }//setChartMarginColor(...) @@ -1717,8 +1718,9 @@ void D3SpectrumDisplayDiv::setChartBackgroundColor( const Wt::WColor &color ) if( m_cssRules.count(rulename) ) style.removeRule( m_cssRules[rulename] ); - - m_cssRules[rulename] = style.addRule( "#chartarea" + id(), "fill: " + c ); + + m_cssRules[rulename] = style.addRule( ":root", "--d3spec-chart-area-color: " + c + ";" ); + //m_cssRules[rulename] = style.addRule( "#chartarea" + id(), "fill: " + c ); //If we wanted to apply this color to only this chart } void D3SpectrumDisplayDiv::setDefaultPeakColor( const Wt::WColor &color ) @@ -2583,6 +2585,26 @@ void D3SpectrumDisplayDiv::sliderChartDisplayedCallback( const bool madeVisisble }//void sliderChartDisplayedCallback( const bool madeVisisble ); +void D3SpectrumDisplayDiv::yAxisTypeChangedCallback( const std::string &type ) +{ + const bool isLogY = (type == "log"); + if( isLogY == m_yAxisIsLog ) + return; + + m_yAxisIsLog = isLogY; + InterSpec *interspec = InterSpec::instance(); + interspec->setLogY( m_yAxisIsLog ); //toggles menu items, but wont put in undo/redo step + + UndoRedoManager *undoRedo = UndoRedoManager::instance(); + if( undoRedo && undoRedo->canAddUndoRedoNow() ) + { + undoRedo->addUndoRedoStep( [isLogY](){ InterSpec::instance()->setLogY(!isLogY); }, + [isLogY](){ InterSpec::instance()->setLogY(isLogY); }, + "Toggle log-y axis" ); + } +}//void yAxisTypeChanged( const std::string &type ) + + D3SpectrumDisplayDiv::~D3SpectrumDisplayDiv() { //doJavaScript( "try{" + m_jsgraph + "=null;}catch(){}" ); diff --git a/src/DetectionLimitCalc.cpp b/src/DetectionLimitCalc.cpp index 78a62039..e2f9c538 100644 --- a/src/DetectionLimitCalc.cpp +++ b/src/DetectionLimitCalc.cpp @@ -800,10 +800,14 @@ CurrieMdaResult currie_mda_calc( const CurrieMdaInput &input ) || (input.gamma_energy > input.roi_upper_energy) ) throw runtime_error( "mda_counts_calc: gamma energy must be between lower and upper ROI." ); - if( input.num_lower_side_channels < 1 || (input.num_lower_side_channels >= nchannel) ) + if( ((input.num_lower_side_channels == 0) || (input.num_upper_side_channels == 0)) + && (input.num_lower_side_channels != input.num_upper_side_channels) ) + throw runtime_error( "mda_counts_calc: lower or upper side channels was zero, but not both." ); + + if( input.num_lower_side_channels >= nchannel ) throw runtime_error( "mda_counts_calc: invalid num_lower_side_channels." ); - if( input.num_upper_side_channels < 1 || (input.num_upper_side_channels >= nchannel) ) + if( input.num_upper_side_channels >= nchannel ) throw runtime_error( "mda_counts_calc: invalid num_upper_side_channels." ); if( input.detection_probability <= 0.05 || input.detection_probability >= 1.0 ) @@ -828,18 +832,36 @@ CurrieMdaResult currie_mda_calc( const CurrieMdaInput &input ) if( result.first_peak_region_channel < (input.num_lower_side_channels + 1) ) throw std::runtime_error( "mda_counts_calc: lower peak region is outside spectrum energy range" ); - result.last_lower_continuum_channel = result.first_peak_region_channel - 1; - result.first_lower_continuum_channel = result.last_lower_continuum_channel - input.num_lower_side_channels + 1; - - result.first_upper_continuum_channel = result.last_peak_region_channel + 1; - result.last_upper_continuum_channel = result.first_upper_continuum_channel + input.num_upper_side_channels - 1; - - if( result.last_upper_continuum_channel >= nchannel ) - throw std::runtime_error( "mda_counts_calc: upper peak region is outside spectrum energy range" ); + if( input.num_lower_side_channels == 0 ) + { + result.first_lower_continuum_channel = 0; + result.last_lower_continuum_channel = 0; + result.lower_continuum_counts_sum = 0; + }else + { + result.last_lower_continuum_channel = result.first_peak_region_channel - 1; + result.first_lower_continuum_channel = result.last_lower_continuum_channel - input.num_lower_side_channels + 1; + result.lower_continuum_counts_sum = spec->gamma_channels_sum(result.first_lower_continuum_channel, result.last_lower_continuum_channel); + } - result.lower_continuum_counts_sum = spec->gamma_channels_sum(result.first_lower_continuum_channel, result.last_lower_continuum_channel); + if( input.num_upper_side_channels == 0 ) + { + result.first_upper_continuum_channel = 0; + result.last_upper_continuum_channel = 0; + result.upper_continuum_counts_sum = 0; + }else + { + result.first_upper_continuum_channel = result.last_peak_region_channel + 1; + result.last_upper_continuum_channel = result.first_upper_continuum_channel + input.num_upper_side_channels - 1; + + if( result.last_upper_continuum_channel >= nchannel ) + throw std::runtime_error( "mda_counts_calc: upper peak region is outside spectrum energy range" ); + + result.upper_continuum_counts_sum = spec->gamma_channels_sum(result.first_upper_continuum_channel, result.last_upper_continuum_channel); + } + result.peak_region_counts_sum = spec->gamma_channels_sum(result.first_peak_region_channel, result.last_peak_region_channel); - result.upper_continuum_counts_sum = spec->gamma_channels_sum(result.first_upper_continuum_channel, result.last_upper_continuum_channel); + /* cout << "Lower region:\n\tChan\tEne\tCounts" << endl; @@ -858,65 +880,80 @@ CurrieMdaResult currie_mda_calc( const CurrieMdaInput &input ) cout << "\tSum: " << result.upper_continuum_counts_sum << endl; */ - const double lower_cont_counts = spec->gamma_channels_sum(result.first_lower_continuum_channel, result.last_lower_continuum_channel); - const double upper_cont_counts = spec->gamma_channels_sum(result.first_upper_continuum_channel, result.last_upper_continuum_channel); - const double lower_cont_width = spec->gamma_channel_upper(result.last_lower_continuum_channel) - - spec->gamma_channel_lower(result.first_lower_continuum_channel); - const double upper_cont_width = spec->gamma_channel_upper(result.last_upper_continuum_channel) - - spec->gamma_channel_lower(result.first_upper_continuum_channel); - - const double lower_cont_density = lower_cont_counts / lower_cont_width; - const double lower_cont_density_uncert = ((lower_cont_counts <= 0.0) ? 0.0 : (lower_cont_density / sqrt(lower_cont_counts))); - - const double upper_cont_density = upper_cont_counts / upper_cont_width; - const double upper_cont_density_uncert = ((upper_cont_counts <= 0.0) ? 0.0 : (upper_cont_density / sqrt(upper_cont_counts))); - - const double peak_cont_density = 0.5*(lower_cont_density + upper_cont_density); - const double peak_cont_density_uncert = 0.5*sqrt( upper_cont_density_uncert*upper_cont_density_uncert - + lower_cont_density_uncert*lower_cont_density_uncert ); - const double peak_cont_frac_uncert = ((peak_cont_density > 0.0) ? (peak_cont_density_uncert / peak_cont_density) : 1.0); - - - const double peak_area_width = spec->gamma_channel_upper(result.last_peak_region_channel) - - spec->gamma_channel_lower(result.first_peak_region_channel); - const double peak_cont_sum = peak_cont_density * peak_area_width; - const double peak_cont_sum_uncert = peak_cont_sum * peak_cont_frac_uncert; - - result.estimated_peak_continuum_counts = static_cast( peak_cont_sum ); - result.estimated_peak_continuum_uncert = static_cast( peak_cont_sum_uncert ); + double peak_cont_sum_uncert = -999.9f, peak_cont_sum = -999.9f; - - // The equation is centered around the input.gamma_energy with the density of counts at normal - // value at that point. The Slope will be through the midpoints of each continuum. - // TODO: should do a proper least-squares fit to the continuum; I think this will give us a slightly true-er answer - - const double lower_cont_mid_energy = spec->gamma_channel_lower(result.first_lower_continuum_channel) + 0.5*lower_cont_width; - const double upper_cont_mid_energy = spec->gamma_channel_lower(result.first_upper_continuum_channel) + 0.5*upper_cont_width; - - result.continuum_eqn[1] = (upper_cont_density - lower_cont_density) / (upper_cont_mid_energy - lower_cont_mid_energy); - result.continuum_eqn[0] = lower_cont_density - result.continuum_eqn[1]*(lower_cont_mid_energy - input.gamma_energy); - -#if( PERFORM_DEVELOPER_CHECKS ) - {// begin sanity check on continuum eqn - const double peak_start_eq = spec->gamma_channel_lower(result.first_peak_region_channel) - input.gamma_energy; - const double peak_end_eq = spec->gamma_channel_upper(result.last_peak_region_channel) - input.gamma_energy; + if( input.num_upper_side_channels == 0 ) + { + peak_cont_sum = result.peak_region_counts_sum; + peak_cont_sum_uncert = sqrt( peak_cont_sum ); + + const double peak_area_width = spec->gamma_channel_upper(result.last_peak_region_channel) + - spec->gamma_channel_lower(result.first_peak_region_channel); + result.continuum_eqn[1] = 0.0; + result.continuum_eqn[0] = peak_cont_sum / peak_area_width; + }else + { + const double lower_cont_counts = spec->gamma_channels_sum(result.first_lower_continuum_channel, result.last_lower_continuum_channel); + const double upper_cont_counts = spec->gamma_channels_sum(result.first_upper_continuum_channel, result.last_upper_continuum_channel); + const double lower_cont_width = spec->gamma_channel_upper(result.last_lower_continuum_channel) + - spec->gamma_channel_lower(result.first_lower_continuum_channel); + const double upper_cont_width = spec->gamma_channel_upper(result.last_upper_continuum_channel) + - spec->gamma_channel_lower(result.first_upper_continuum_channel); + + const double lower_cont_density = lower_cont_counts / lower_cont_width; + const double lower_cont_density_uncert = ((lower_cont_counts <= 0.0) ? 0.0 : (lower_cont_density / sqrt(lower_cont_counts))); + + const double upper_cont_density = upper_cont_counts / upper_cont_width; + const double upper_cont_density_uncert = ((upper_cont_counts <= 0.0) ? 0.0 : (upper_cont_density / sqrt(upper_cont_counts))); - const double peak_cont_eq_integral = result.continuum_eqn[0] * (peak_end_eq - peak_start_eq) - + result.continuum_eqn[1] * 0.5 * (peak_end_eq*peak_end_eq - peak_start_eq*peak_start_eq); - const double upper_cont_eq = result.continuum_eqn[0] + (upper_cont_mid_energy - input.gamma_energy)*result.continuum_eqn[1]; + const double peak_cont_density = 0.5*(lower_cont_density + upper_cont_density); + const double peak_cont_density_uncert = 0.5*sqrt( upper_cont_density_uncert*upper_cont_density_uncert + + lower_cont_density_uncert*lower_cont_density_uncert ); + const double peak_cont_frac_uncert = ((peak_cont_density > 0.0) ? (peak_cont_density_uncert / peak_cont_density) : 1.0); - // Precision tests, for development - if we go down to a precision of 1E-4, instead of 1E-3, - // then these tests fail for NaI systems - I'm not sure if its something actually wrong, or - // just really bad numerical accuracy (although its hard to imagine going down to only 4 - // or so, significant figures) - const double eq_dens = fabs(peak_cont_eq_integral - peak_cont_sum); - assert( (eq_dens < 0.1) - || (eq_dens < 1.0E-3*std::max(peak_cont_eq_integral, peak_cont_sum)) ); + const double peak_area_width = spec->gamma_channel_upper(result.last_peak_region_channel) + - spec->gamma_channel_lower(result.first_peak_region_channel); - const double eq_diff = fabs(peak_cont_eq_integral - peak_cont_sum); - assert( eq_diff < 0.1 || eq_diff < 1.0E-3*std::max(peak_cont_eq_integral, peak_cont_sum) ); - }// end sanity check on continuum eqn + peak_cont_sum = peak_cont_density * peak_area_width; + peak_cont_sum_uncert = peak_cont_sum * peak_cont_frac_uncert; + + // The equation is centered around the input.gamma_energy with the density of counts at normal + // value at that point. The Slope will be through the midpoints of each continuum. + // TODO: should do a proper least-squares fit to the continuum; I think this will give us a slightly true-er answer + + const double lower_cont_mid_energy = spec->gamma_channel_lower(result.first_lower_continuum_channel) + 0.5*lower_cont_width; + const double upper_cont_mid_energy = spec->gamma_channel_lower(result.first_upper_continuum_channel) + 0.5*upper_cont_width; + + result.continuum_eqn[1] = (upper_cont_density - lower_cont_density) / (upper_cont_mid_energy - lower_cont_mid_energy); + result.continuum_eqn[0] = lower_cont_density - result.continuum_eqn[1]*(lower_cont_mid_energy - input.gamma_energy); + +#if( PERFORM_DEVELOPER_CHECKS ) + {// begin sanity check on continuum eqn + const double peak_start_eq = spec->gamma_channel_lower(result.first_peak_region_channel) - input.gamma_energy; + const double peak_end_eq = spec->gamma_channel_upper(result.last_peak_region_channel) - input.gamma_energy; + + const double peak_cont_eq_integral = result.continuum_eqn[0] * (peak_end_eq - peak_start_eq) + + result.continuum_eqn[1] * 0.5 * (peak_end_eq*peak_end_eq - peak_start_eq*peak_start_eq); + const double upper_cont_eq = result.continuum_eqn[0] + (upper_cont_mid_energy - input.gamma_energy)*result.continuum_eqn[1]; + + // Precision tests, for development - if we go down to a precision of 1E-4, instead of 1E-3, + // then these tests fail for NaI systems - I'm not sure if its something actually wrong, or + // just really bad numerical accuracy (although its hard to imagine going down to only 4 + // or so, significant figures) + const double eq_dens = fabs(peak_cont_eq_integral - peak_cont_sum); + assert( (eq_dens < 0.1) + || (eq_dens < 1.0E-3*std::max(peak_cont_eq_integral, peak_cont_sum)) ); + + const double eq_diff = fabs(peak_cont_eq_integral - peak_cont_sum); + assert( eq_diff < 0.1 || eq_diff < 1.0E-3*std::max(peak_cont_eq_integral, peak_cont_sum) ); + }// end sanity check on continuum eqn #endif //PERFORM_DEVELOPER_CHECKS + }//if( input.num_upper_side_channels == 0 ) / else + + assert( peak_cont_sum_uncert != -999.9f ); + assert( peak_cont_sum != -999.9f ); + result.estimated_peak_continuum_counts = static_cast( peak_cont_sum ); + result.estimated_peak_continuum_uncert = static_cast( peak_cont_sum_uncert ); typedef boost::math::policies::policy > my_pol_6; const boost::math::normal_distribution gaus_dist( 0.0, 1.0 ); diff --git a/src/DetectionLimitSimple.cpp b/src/DetectionLimitSimple.cpp index b20ca3f3..97fb0e2b 100644 --- a/src/DetectionLimitSimple.cpp +++ b/src/DetectionLimitSimple.cpp @@ -30,9 +30,11 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -211,6 +213,8 @@ DetectionLimitSimple::DetectionLimitSimple( MaterialDB *materialDB, m_fwhmSuggestTxt( nullptr ), m_addFwhmBtn( nullptr ), m_selectDetectorBtn( nullptr ), + m_isBackgroundDiv( nullptr ), + m_isBackgroundSpectrum( nullptr ), m_continuumPriorLabel( nullptr ), m_continuumPrior( nullptr ), m_continuumTypeLabel( nullptr ), @@ -384,18 +388,20 @@ void DetectionLimitSimple::init() switch( cl ) { - case ConfidenceLevel::OneSigma: txt = "68%"; break; - case ConfidenceLevel::TwoSigma: txt = "95%"; break; - case ConfidenceLevel::ThreeSigma: txt = "99%"; break; - case ConfidenceLevel::FourSigma: txt = "4-sigma"; break; - case ConfidenceLevel::FiveSigma: txt = "5-sigma"; break; - case ConfidenceLevel::NumConfidenceLevel: break; + case ConfidenceLevel::NinetyFivePercent: txt = "95%"; break; + case ConfidenceLevel::NinetyNinePercent: txt = "99%"; break; + case ConfidenceLevel::OneSigma: txt = "1σ (68.2%)"; break; + case ConfidenceLevel::TwoSigma: txt = "2σ (95.4%)"; break; + case ConfidenceLevel::ThreeSigma: txt = "3σ (99.7%)"; break; + case ConfidenceLevel::FourSigma: txt = "4σ"; break; //1-6.3E-5 + case ConfidenceLevel::FiveSigma: txt = "5σ"; break; //1-5.7E-7 + case ConfidenceLevel::NumConfidenceLevel: assert( 0 ); break; }//switch( cl ) m_confidenceLevel->addItem( txt ); }//for( loop over confidence levels ) - m_confidenceLevel->setCurrentIndex( ConfidenceLevel::TwoSigma ); + m_confidenceLevel->setCurrentIndex( ConfidenceLevel::NinetyFivePercent ); m_confidenceLevel->activated().connect(this, &DetectionLimitSimple::handleConfidenceLevelChanged ); @@ -464,7 +470,24 @@ void DetectionLimitSimple::init() m_addFwhmBtn->setHidden( !drf || !drf->isValid() || drf->hasResolutionInfo() ); m_selectDetectorBtn->setHidden( drf && drf->isValid() ); - m_continuumPriorLabel = new WLabel( WString::tr("dls-deon-cont-norm-label"), generalInput ); + m_isBackgroundDiv = new WContainerWidget( generalInput ); + m_isBackgroundDiv->addStyleClass( "BackCbDiv GridFirstCol GridSeventhRow GridVertCenter GridSpanTwoCol" ); + m_isBackgroundSpectrum = new WCheckBox( WString::tr("dls-is-background-spectrum-cb"), m_isBackgroundDiv ); + m_isBackgroundSpectrum->checked().connect( this, &DetectionLimitSimple::handleNoSignalPresentChanged ); + m_isBackgroundSpectrum->unChecked().connect( this, &DetectionLimitSimple::handleNoSignalPresentChanged ); + + { + m_isBackgroundSpectrum->setWordWrap( false ); + WImage *img = new WImage( m_isBackgroundDiv ); + img->setImageLink(Wt::WLink("InterSpec_resources/images/help_minimal.svg") ); + img->resize( 16, 16 ); //setStyleClass("Wt-icon"); + img->decorationStyle().setCursor( Wt::Cursor::WhatsThisCursor ); + WString tt = WString::tr("dls-is-background-spectrum-tt"); + HelpSystem::attachToolTipOn( img, tt, true, HelpSystem::ToolTipPosition::Right, + HelpSystem::ToolTipPrefOverride::InstantAlways ); + } + + m_continuumPriorLabel = new WLabel( WString::tr("dls-decon-cont-norm-label"), generalInput ); m_continuumPriorLabel->addStyleClass( "GridFirstCol GridSeventhRow GridVertCenter" ); m_continuumPrior = new WComboBox( generalInput ); m_continuumPrior->addItem( WString::tr("dls-cont-norm-unknown") ); @@ -474,8 +497,10 @@ void DetectionLimitSimple::init() m_continuumPrior->activated().connect( this, &DetectionLimitSimple::handleDeconPriorChange ); m_continuumPrior->addStyleClass( "ContTypeCombo GridSecondCol GridSeventhRow" ); - m_continuumPriorLabel->setHiddenKeepsGeometry( true ); - m_continuumPrior->setHiddenKeepsGeometry( true ); + //m_continuumPriorLabel->setHiddenKeepsGeometry( true ); + //m_continuumPrior->setHiddenKeepsGeometry( true ); + m_continuumPriorLabel->hide(); + m_continuumPrior->hide(); m_continuumTypeLabel = new WLabel( "Continuum Type:", generalInput ); m_continuumTypeLabel->addStyleClass( "GridFourthCol GridSeventhRow GridVertCenter" ); @@ -485,11 +510,8 @@ void DetectionLimitSimple::init() m_continuumType->setCurrentIndex( 0 ); m_continuumType->activated().connect( this, &DetectionLimitSimple::handleDeconContinuumTypeChange ); m_continuumType->addStyleClass( "GridFifthCol GridSeventhRow" ); - - m_continuumPriorLabel->hide(); - m_continuumPrior->hide(); - m_continuumTypeLabel->hide(); - m_continuumType->hide(); + m_continuumTypeLabel->setHidden( true ); + m_continuumType->setHidden( true ); WContainerWidget *container = new WContainerWidget( generalInput ); container->addStyleClass( "MethodSelect GridFirstCol GridEighthRow GridSpanFiveCol" ); @@ -720,6 +742,9 @@ void DetectionLimitSimple::handleDeconPriorChange() m_numSideChannelLabel->setHidden( !currieMethod && !useSideChan ); m_numSideChannel->setHidden( !currieMethod && !useSideChan ); + m_continuumTypeLabel->setHidden( useSideChan ); + m_continuumType->setHidden( useSideChan ); + m_renderFlags |= DetectionLimitSimple::RenderActions::UpdateLimit; m_renderFlags |= DetectionLimitSimple::RenderActions::AddUndoRedoStep; m_renderFlags |= DetectionLimitSimple::RenderActions::UpdateSpectrumDecorations; @@ -727,6 +752,22 @@ void DetectionLimitSimple::handleDeconPriorChange() }//void handleDeconPriorChange() +void DetectionLimitSimple::handleNoSignalPresentChanged() +{ + const bool noSignal = m_isBackgroundSpectrum->isChecked(); + const bool currieMethod = (m_methodGroup->checkedId() == static_cast(MethodIds::Currie)); + assert( currieMethod ); + + m_numSideChannelLabel->setHidden( !currieMethod || noSignal ); + m_numSideChannel->setHidden( !currieMethod || noSignal ); + + m_renderFlags |= DetectionLimitSimple::RenderActions::UpdateLimit; + m_renderFlags |= DetectionLimitSimple::RenderActions::AddUndoRedoStep; + m_renderFlags |= DetectionLimitSimple::RenderActions::UpdateSpectrumDecorations; + scheduleRender(); +}//void handleNoSignalPresentChanged() + + void DetectionLimitSimple::handleDeconContinuumTypeChange() { m_renderFlags |= DetectionLimitSimple::RenderActions::UpdateLimit; @@ -745,17 +786,26 @@ void DetectionLimitSimple::handleMethodChanged() { const bool currieMethod = (m_methodGroup->checkedId() == static_cast(MethodIds::Currie)); - m_numSideChannelLabel->setHidden( !currieMethod ); - m_numSideChannel->setHidden( !currieMethod ); - - const bool useSideChan = (m_continuumPrior->currentIndex() == 2); - m_numSideChannelLabel->setHidden( !currieMethod && !useSideChan ); - m_numSideChannel->setHidden( !currieMethod && !useSideChan ); - + m_isBackgroundDiv->setHidden( !currieMethod ); m_continuumPriorLabel->setHidden( currieMethod ); m_continuumPrior->setHidden( currieMethod ); - m_continuumTypeLabel->setHidden( currieMethod ); - m_continuumType->setHidden( currieMethod ); + + if( currieMethod ) + { + const bool noSignal = m_isBackgroundSpectrum->isChecked(); + m_numSideChannelLabel->setHidden( noSignal ); + m_numSideChannel->setHidden( noSignal ); + + m_continuumTypeLabel->setHidden( true ); + m_continuumType->setHidden( true ); + }else + { + const bool useSideChan = (m_continuumPrior->currentIndex() == 2); + m_numSideChannelLabel->setHidden( !useSideChan ); + m_numSideChannel->setHidden( !useSideChan ); + m_continuumTypeLabel->setHidden( useSideChan ); + m_continuumType->setHidden( useSideChan ); + }//if( currieMethod ) / else m_methodDescription->setText( WString::tr(currieMethod ? "dls-currie-desc" : "dls-decon-desc") ); @@ -1156,6 +1206,9 @@ void DetectionLimitSimple::updateSpectrumDecorationsAndResultText() })(); const bool currieMethod = (m_methodGroup->checkedId() == static_cast(MethodIds::Currie)); + + assert( m_isBackgroundDiv->isVisible() == currieMethod ); + if( currieMethod ) { // Currie method limit @@ -1273,8 +1326,17 @@ void DetectionLimitSimple::updateSpectrumDecorationsAndResultText() const bool use_curie = use_curie_units(); const DetectorPeakResponse::EffGeometryType det_geom = drf ? drf->geometryType() : DetectorPeakResponse::EffGeometryType::FarField; + assert( (result->input.num_lower_side_channels != 0) + || (result->input.num_upper_side_channels != 0) + || (result->input.num_lower_side_channels == result->input.num_upper_side_channels) ); + + const bool assertedIsBackground = ((result->input.num_lower_side_channels == 0) + && (result->input.num_upper_side_channels == 0)); + if( result->source_counts > result->decision_threshold ) { + assert( !assertedIsBackground ); + // There is enough excess counts that we would reliably detect this activity, so we will // give the activity range. string lowerstr, upperstr, nomstr; @@ -1303,6 +1365,7 @@ void DetectionLimitSimple::updateSpectrumDecorationsAndResultText() } }else if( result->upper_limit < 0 ) { + assert( !assertedIsBackground ); // This can happen when there are a lot fewer counts in the peak region than predicted // from the sides - since this is non-sensical, we'll just say zero. const string unitstr = use_curie ? "Ci" : "Bq"; @@ -1331,8 +1394,16 @@ void DetectionLimitSimple::updateSpectrumDecorationsAndResultText() result_txt = WString::tr("dls-det-upper-bound").arg(mdastr).arg(cl_str); }//if( detected ) / else if( ....) - WString full_result_txt( "{1}
{2}" ); - full_result_txt.arg(result_txt); + WString full_result_txt; + + if( assertedIsBackground ) + { + full_result_txt = WString( "{1}" ); + }else + { + full_result_txt = WString( "{1}
{2}" ); + full_result_txt.arg(result_txt); + }//if( assertedIsBackground ) / else if( gammas_per_bq > 0.0 ) { @@ -1495,12 +1566,14 @@ double DetectionLimitSimple::currentConfidenceLevel() const switch( confidence ) { - case OneSigma: confidenceLevel = 0.682689492137086; break; - case TwoSigma: confidenceLevel = 0.954499736103642; break; - case ThreeSigma: confidenceLevel = 0.997300203936740; break; - case FourSigma: confidenceLevel = 0.999936657516334; break; - case FiveSigma: confidenceLevel = 0.999999426696856; break; - case NumConfidenceLevel: assert(0); break; + case ConfidenceLevel::NinetyFivePercent: confidenceLevel = 0.95; break; + case ConfidenceLevel::NinetyNinePercent: confidenceLevel = 0.99; break; + case ConfidenceLevel::OneSigma: confidenceLevel = 0.682689492137086; break; + case ConfidenceLevel::TwoSigma: confidenceLevel = 0.954499736103642; break; + case ConfidenceLevel::ThreeSigma: confidenceLevel = 0.997300203936740; break; + case ConfidenceLevel::FourSigma: confidenceLevel = 0.999936657516334; break; + case ConfidenceLevel::FiveSigma: confidenceLevel = 0.999999426696856; break; + case ConfidenceLevel::NumConfidenceLevel: assert(0); break; }//switch( confidence ) return confidenceLevel; @@ -1938,6 +2011,8 @@ void DetectionLimitSimple::updateResult() if( !hist || (hist->num_gamma_channels() < 7) ) throw runtime_error( "No foreground spectrum loaded." ); + const bool currieMethod = (m_methodGroup->checkedId() == static_cast(MethodIds::Currie)); + const float roi_lower_energy = m_lowerRoi->value(); const float roi_upper_energy = m_upperRoi->value(); @@ -1962,6 +2037,12 @@ void DetectionLimitSimple::updateResult() currie_input->roi_upper_energy = m_upperRoi->value(); currie_input->num_lower_side_channels = static_cast( m_numSideChannel->value() ); currie_input->num_upper_side_channels = currie_input->num_lower_side_channels; + if( currieMethod && m_isBackgroundSpectrum->isChecked() ) + { + currie_input->num_lower_side_channels = 0; + currie_input->num_upper_side_channels = 0; + } + currie_input->detection_probability = confidenceLevel; currie_input->additional_uncertainty = 0.0f; // TODO: can we get the DRFs contribution to form this? @@ -1972,7 +2053,6 @@ void DetectionLimitSimple::updateResult() // Calculating the deconvolution-style limit is fairly CPU intensive, so we will only computer // it when its what the user actually wants. - const bool currieMethod = (m_methodGroup->checkedId() == static_cast(MethodIds::Currie)); if( !currieMethod ) { const float live_time = m_currentCurrieInput->spectrum->live_time(); @@ -2033,7 +2113,6 @@ void DetectionLimitSimple::updateResult() throw std::logic_error( "Invalid continuuuum type selected" ); }//switch( continuumTypeIndex ) - const int continuumPriorIndex = m_continuumPrior->currentIndex(); switch( continuumPriorIndex ) { @@ -2046,6 +2125,7 @@ void DetectionLimitSimple::updateResult() break; case 2: // "Cont. from sides" + roiInfo.continuum_type = PeakContinuum::OffsetType::Linear; roiInfo.cont_norm_method = DetectionLimitCalc::DeconContinuumNorm::FixedByEdges; break; @@ -2054,6 +2134,11 @@ void DetectionLimitSimple::updateResult() throw std::logic_error( "Invalid continuuuum prior selected" ); }//switch( continuumPriorIndex ) + //if( assertNoSignal ) + // roiInfo.cont_norm_method = DetectionLimitCalc::DeconContinuumNorm::FixedByFullRange; // "Not Present" + //else + // roiInfo.cont_norm_method = DetectionLimitCalc::DeconContinuumNorm::Floating; // "Unknown or Present" + if( roiInfo.cont_norm_method == DetectionLimitCalc::DeconContinuumNorm::FixedByEdges ) { // `roiInfo.num_*_side_channels` only used if `cont_norm_method` is `DeconContinuumNorm::FixedByEdges`. @@ -2178,6 +2263,13 @@ void DetectionLimitSimple::updateResult() const double simple_mda = currie_result.upper_limit / gammas_per_bq; min_act = 0.0; max_act = diff_multiple*simple_mda; + + //if( assertNoSignal ) + //{ + // assert( currie_result.source_counts == 0.0f ); + // const double niave_max = currie_result.peak_region_counts_sum / gammas_per_bq; + // max_act = diff_multiple * std::max( simple_mda, niave_max ); + //} }//if( detected signal ) / else / else }// end estimate range we should search for deconvolution @@ -2244,6 +2336,8 @@ void DetectionLimitSimple::handleAppUrl( std::string uri ) handleMethodChanged(); }//if( m_methodGroup->checkedId() != static_cast(methodIndex) ) + const bool currieMethod = (m_methodGroup->checkedId() == static_cast(MethodIds::Currie)); + auto qpos = values.find( "VER" ); const string ver = ((qpos != end(values)) && !qpos->second.empty()) ? qpos->second : "1"; @@ -2368,43 +2462,63 @@ void DetectionLimitSimple::handleAppUrl( std::string uri ) if( qpos != end(values) ) { int nsigma; - if( (stringstream(qpos->second) >> nsigma) && (nsigma >= 1) && (nsigma <= 5) ) + if( (stringstream(qpos->second) >> nsigma) ) { - m_confidenceLevel->setCurrentIndex( nsigma - 1 ); + ConfidenceLevel cl = ConfidenceLevel::NinetyFivePercent; + + switch( nsigma ) + { + case 1: cl = ConfidenceLevel::OneSigma; break; + case 2: cl = ConfidenceLevel::TwoSigma; break; + case 3: cl = ConfidenceLevel::ThreeSigma; break; + case 4: cl = ConfidenceLevel::FourSigma; break; + case 5: cl = ConfidenceLevel::FiveSigma; break; + case 95: cl = ConfidenceLevel::NinetyFivePercent; break; + case 99: cl = ConfidenceLevel::NinetyNinePercent; break; + default: + cerr << "Invalid URI CL, '" << qpos->second << "' to a valid CL." << endl; + break; + }//switch( nsigma ) + + m_confidenceLevel->setCurrentIndex( static_cast(cl) ); }else { - cerr << "Failed to convert URI CL, '" << qpos->second << "' to a to an int between 1 and 5" << endl; + cerr << "Failed to convert URI CL, '" << qpos->second << "' to a to an integer." << endl; } }//if( URI has CL ) - - int continuumNormIndex = -1; - qpos = values.find( "CONTNORM" ); - if( qpos != end(values) ) - { - if( SpecUtils::istarts_with(qpos->second, "UNKN") ) - continuumNormIndex = 0; - else if( SpecUtils::istarts_with(qpos->second, "NOS") ) - continuumNormIndex = 1; - else if( SpecUtils::istarts_with(qpos->second, "FIX") ) - continuumNormIndex = 2; - else - cerr << "Unexpected 'CONTNORM' value: '" << qpos->second << "'" << endl; - }//if( URI had continuum norm value ) - - if( (m_methodGroup->checkedId() == static_cast(MethodIds::Currie)) - || (continuumNormIndex >= 0) ) + if( currieMethod ) { + bool noSignal = false; + qpos = values.find( "ISBACK" ); + if( qpos != end(values) ) + { + if( (qpos->second == "0") || SpecUtils::iequals_ascii(qpos->second, "NO") || SpecUtils::iequals_ascii(qpos->second, "FALSE") ) + noSignal = false; + else if( (qpos->second == "1") || SpecUtils::iequals_ascii(qpos->second, "YES") || SpecUtils::iequals_ascii(qpos->second, "TRUE") ) + noSignal = true; + else + cerr << "Unexpected 'ISBACK' value: '" << qpos->second << "' (non-bool)" << endl; + }//if( URI had is background value ) + + m_isBackgroundSpectrum->setChecked( noSignal ); + m_numSideChannelLabel->setHidden( noSignal ); + m_numSideChannel->setHidden( noSignal ); + qpos = values.find( "NSIDE" ); if( qpos != end(values) ) { + // In principle we shouldnt have the NSIDE argument if the "is background" checkbox is checked + // but whatever. + assert( !noSignal ); + int nside; if( (stringstream(qpos->second) >> nside) && (nside >= 1) && (nside <= 64) ) m_numSideChannel->setValue( nside ); else cerr << "Invalid 'NSIDE' value: '" << qpos->second << "'" << endl; }//if( URI has NSIDE ) - }//if( want value of number of side channels ) + }//if( currieMethod ) bool setFwhm = false; qpos = values.find( "FWHM" ); @@ -2427,10 +2541,32 @@ void DetectionLimitSimple::handleAppUrl( std::string uri ) handleUserChangedFwhm(); } - if( m_methodGroup->checkedId() == static_cast(MethodIds::Deconvolution) ) + if( !currieMethod ) { - if( continuumNormIndex >= 0 ) - m_continuumPrior->setCurrentIndex( continuumNormIndex ); + qpos = values.find( "CONTNORM" ); + if( qpos != end(values) ) + { + int priorTypeIndex = -1; + if( SpecUtils::istarts_with(qpos->second, "UNK") ) + priorTypeIndex = 0; + else if( SpecUtils::istarts_with(qpos->second, "NOS") ) + priorTypeIndex = 1; + else if( SpecUtils::istarts_with(qpos->second, "FIX") ) + priorTypeIndex = 2; + else + cerr << "Invalid 'CONTNORM' value: '" << qpos->second << "'" << endl; + + if( priorTypeIndex >= 0 ) + { + m_continuumPrior->setCurrentIndex( priorTypeIndex ); + + m_numSideChannelLabel->setHidden( priorTypeIndex != 2 ); + m_numSideChannel->setHidden( priorTypeIndex != 2 ); + + m_continuumTypeLabel->setHidden( priorTypeIndex == 2 ); + m_continuumType->setHidden( priorTypeIndex == 2 ); + }//if( prior type provided ) + }//if( continuum prior provided ) qpos = values.find( "CONTTYPE" ); @@ -2536,19 +2672,20 @@ std::string DetectionLimitSimple::encodeStateToUrl() const switch( confidence ) { - case OneSigma: answer += "1"; break; - case TwoSigma: answer += "2"; break; - case ThreeSigma: answer += "3"; break; - case FourSigma: answer += "4"; break; - case FiveSigma: answer += "5"; break; - case NumConfidenceLevel: assert(0); break; + case ConfidenceLevel::NinetyFivePercent: answer += "95"; break; + case ConfidenceLevel::NinetyNinePercent: answer += "99"; break; + case ConfidenceLevel::OneSigma: answer += "1"; break; + case ConfidenceLevel::TwoSigma: answer += "2"; break; + case ConfidenceLevel::ThreeSigma: answer += "3"; break; + case ConfidenceLevel::FourSigma: answer += "4"; break; + case ConfidenceLevel::FiveSigma: answer += "5"; break; + case ConfidenceLevel::NumConfidenceLevel: assert(0); break; }//switch( confidence ) const bool useSideChan = (m_continuumPrior->currentIndex() == 2); - if( currieMethod || useSideChan ) + if( (currieMethod && !m_isBackgroundSpectrum->isChecked()) || useSideChan ) answer += "&NSIDE=" + std::to_string(m_numSideChannel->value()); - shared_ptr drf = m_detectorDisplay->detector(); if( drf && (!drf->isValid() || !drf->hasResolutionInfo()) ) drf.reset(); @@ -2558,7 +2695,11 @@ std::string DetectionLimitSimple::encodeStateToUrl() const if( !drf || (fabs(m_fwhm->value() - drf->peakResolutionFWHM(energy)) > 0.1) ) answer += "&FWHM=" + m_fwhm->valueText().toUTF8(); - if( !currieMethod ) + if( currieMethod ) + { + const bool noSignal = m_isBackgroundSpectrum->isChecked(); + answer += "&ISBACK=" + string(noSignal ? "1" : "0"); + }else { answer += "&CONTNORM="; switch( m_continuumPrior->currentIndex() ) @@ -2572,18 +2713,19 @@ std::string DetectionLimitSimple::encodeStateToUrl() const throw logic_error( "Invalid m_continuumPrior" ); }//switch( m_continuumPrior->currentIndex() ) - answer += "&CONTTYPE="; - - const int continuumTypeIndex = m_continuumType->currentIndex(); - switch( continuumTypeIndex ) + if( m_continuumPrior->currentIndex() != 2 ) { - case 0: answer += "LIN"; break; - case 1: answer += "QUAD"; break; - default: - assert( 0 ); - throw std::logic_error( "Invalid continuuuum type selected" ); - }//switch( continuumTypeIndex ) - }//if( !currieMethod ) + answer += "&CONTTYPE="; + switch( m_continuumType->currentIndex() ) + { + case 0: answer += "LIN"; break; + case 1: answer += "QUAD"; break; + default: + assert( 0 ); + throw std::logic_error( "Invalid continuum type selected" ); + }//switch( continuumTypeIndex ) + }//if( m_continuumPrior->currentIndex() != 2 ) + }//if( currieMethod ) / else if( !m_allGammasInRoi ) answer += "&ALLGAMMA=0"; diff --git a/src/DetectionLimitTool.cpp b/src/DetectionLimitTool.cpp index 212335c3..f42a5fd3 100644 --- a/src/DetectionLimitTool.cpp +++ b/src/DetectionLimitTool.cpp @@ -1486,18 +1486,44 @@ void DetectionLimitTool::update_spectrum_for_currie_result( D3SpectrumDisplayDiv double lower_continuum_counts_sum, peak_region_counts_sum, upper_continuum_counts_sum; double lower_lower_energy, lower_upper_energy, upper_lower_energy, upper_upper_energy; + // If number of of side channels is zero, then we expect the following + // variables to all be zero + assert( (result->input.num_lower_side_channels != 0) + || ((result->first_lower_continuum_channel == 0) + && (result->last_lower_continuum_channel == 0) + && (result->lower_continuum_counts_sum == 0) + && (result->first_upper_continuum_channel == 0) + && (result->last_upper_continuum_channel == 0) + && (result->upper_continuum_counts_sum == 0)) + ); + + const bool assertedNoSignal = (result->input.num_lower_side_channels == 0); + // We will prefer to set the highlight regions from the results, but if we dont have results // we'll do it from the input. if( result ) { - lower_lower_energy = spectrum->gamma_channel_lower( result->first_lower_continuum_channel ); - lower_upper_energy = spectrum->gamma_channel_lower( result->first_upper_continuum_channel ); - upper_lower_energy = spectrum->gamma_channel_upper( result->last_lower_continuum_channel ); - upper_upper_energy = spectrum->gamma_channel_upper( result->last_upper_continuum_channel ); - - lower_continuum_counts_sum = result->lower_continuum_counts_sum; - peak_region_counts_sum = result->peak_region_counts_sum; - upper_continuum_counts_sum = result->upper_continuum_counts_sum; + if( assertedNoSignal ) + { + lower_upper_energy = spectrum->gamma_channel_upper( result->last_peak_region_channel ); + upper_upper_energy = lower_upper_energy; + upper_lower_energy = spectrum->gamma_channel_lower( result->first_peak_region_channel ); + lower_lower_energy = upper_lower_energy; + + lower_continuum_counts_sum = 0; + peak_region_counts_sum = result->peak_region_counts_sum; + upper_continuum_counts_sum = 0; + }else + { + lower_lower_energy = spectrum->gamma_channel_lower( result->first_lower_continuum_channel ); + lower_upper_energy = spectrum->gamma_channel_lower( result->first_upper_continuum_channel ); //need + upper_lower_energy = spectrum->gamma_channel_upper( result->last_lower_continuum_channel ); //need + upper_upper_energy = spectrum->gamma_channel_upper( result->last_upper_continuum_channel ); + + lower_continuum_counts_sum = result->lower_continuum_counts_sum; + peak_region_counts_sum = result->peak_region_counts_sum; + upper_continuum_counts_sum = result->upper_continuum_counts_sum; + } }else { const pair channels @@ -1506,28 +1532,46 @@ void DetectionLimitTool::update_spectrum_for_currie_result( D3SpectrumDisplayDiv const size_t first_peak_region_channel = channels.first; const size_t last_peak_region_channel = channels.second; - if( first_peak_region_channel < (input.num_lower_side_channels + 1) ) - throw std::runtime_error( "mda_counts_calc: lower peak region is outside spectrum energy range" ); - - const size_t last_lower_continuum_channel = first_peak_region_channel - 1; - const size_t first_lower_continuum_channel = last_lower_continuum_channel - input.num_lower_side_channels + 1; - - const size_t first_upper_continuum_channel = last_peak_region_channel + 1; - const size_t last_upper_continuum_channel = first_upper_continuum_channel + input.num_upper_side_channels - 1; - - if( last_upper_continuum_channel >= spectrum->num_gamma_channels() ) - throw std::runtime_error( "mda_counts_calc: upper peak region is outside spectrum energy range" ); - - lower_lower_energy = spectrum->gamma_channel_lower( first_lower_continuum_channel ); - lower_upper_energy = spectrum->gamma_channel_lower( first_upper_continuum_channel ); - upper_lower_energy = spectrum->gamma_channel_upper( last_lower_continuum_channel ); - upper_upper_energy = spectrum->gamma_channel_upper( last_upper_continuum_channel ); - - lower_continuum_counts_sum = spectrum->gamma_channels_sum(first_lower_continuum_channel, last_lower_continuum_channel); - peak_region_counts_sum = spectrum->gamma_channels_sum(first_peak_region_channel, last_peak_region_channel); - upper_continuum_counts_sum = spectrum->gamma_channels_sum(first_upper_continuum_channel, last_upper_continuum_channel); + if( assertedNoSignal ) + { + lower_upper_energy = spectrum->gamma_channel_upper( last_peak_region_channel ); + upper_upper_energy = lower_upper_energy; + upper_lower_energy = spectrum->gamma_channel_lower( first_peak_region_channel ); + lower_lower_energy = upper_lower_energy; + + lower_continuum_counts_sum = 0; + peak_region_counts_sum = spectrum->gamma_channels_sum(first_peak_region_channel, last_peak_region_channel); + upper_continuum_counts_sum = 0; + }else + { + if( first_peak_region_channel < (input.num_lower_side_channels + 1) ) + throw std::runtime_error( "mda_counts_calc: lower peak region is outside spectrum energy range" ); + + const size_t last_lower_continuum_channel = first_peak_region_channel - 1; + const size_t first_lower_continuum_channel = last_lower_continuum_channel - input.num_lower_side_channels + 1; + + const size_t first_upper_continuum_channel = last_peak_region_channel + 1; + const size_t last_upper_continuum_channel = first_upper_continuum_channel + input.num_upper_side_channels - 1; + + if( last_upper_continuum_channel >= spectrum->num_gamma_channels() ) + throw std::runtime_error( "mda_counts_calc: upper peak region is outside spectrum energy range" ); + + lower_lower_energy = spectrum->gamma_channel_lower( first_lower_continuum_channel ); + lower_upper_energy = spectrum->gamma_channel_lower( first_upper_continuum_channel ); //need + upper_lower_energy = spectrum->gamma_channel_upper( last_lower_continuum_channel ); //need + upper_upper_energy = spectrum->gamma_channel_upper( last_upper_continuum_channel ); + + lower_continuum_counts_sum = spectrum->gamma_channels_sum(first_lower_continuum_channel, last_lower_continuum_channel); + peak_region_counts_sum = spectrum->gamma_channels_sum(first_peak_region_channel, last_peak_region_channel); + upper_continuum_counts_sum = spectrum->gamma_channels_sum(first_upper_continuum_channel, last_upper_continuum_channel); + }//if( assertedNoSignal ) }//if( result ) / else + assert( !assertedNoSignal + || ((lower_continuum_counts_sum == 0) + && (upper_continuum_counts_sum == 0)) + ); + const double dx = upper_upper_energy - lower_lower_energy; chart->setXAxisRange( lower_lower_energy - 0.5*dx, upper_upper_energy + 0.5*dx ); @@ -1535,29 +1579,34 @@ void DetectionLimitTool::update_spectrum_for_currie_result( D3SpectrumDisplayDiv const int mid_ndec = 1 + static_cast( std::ceil( fabs( std::log10( fabs(peak_region_counts_sum) ) ) ) ); const int upper_ndec = 1 + static_cast( std::ceil( fabs( std::log10( fabs(upper_continuum_counts_sum) ) ) ) ); - const string lower_txt = SpecUtils::printCompact( lower_continuum_counts_sum, lower_ndec ); - const string mid_txt = SpecUtils::printCompact( peak_region_counts_sum, mid_ndec ); - const string upper_txt = SpecUtils::printCompact( upper_continuum_counts_sum, upper_ndec ); - shared_ptr theme = viewer->getColorTheme(); assert( theme ); if( !theme ) throw runtime_error( "Invalid color theme" ); - chart->addDecorativeHighlightRegion( lower_lower_energy, upper_lower_energy, - theme->timeHistoryBackgroundHighlight, - D3SpectrumDisplayDiv::HighlightRegionFill::BelowData, - lower_txt ); + if( !assertedNoSignal ) + { + const string lower_txt = SpecUtils::printCompact( lower_continuum_counts_sum, lower_ndec ); + chart->addDecorativeHighlightRegion( lower_lower_energy, upper_lower_energy, + theme->timeHistoryBackgroundHighlight, + D3SpectrumDisplayDiv::HighlightRegionFill::BelowData, + lower_txt ); + }//if( !assertedNoSignal ) + const string mid_txt = SpecUtils::printCompact( peak_region_counts_sum, mid_ndec ); chart->addDecorativeHighlightRegion( upper_lower_energy, lower_upper_energy, theme->timeHistoryForegroundHighlight, D3SpectrumDisplayDiv::HighlightRegionFill::BelowData, mid_txt ); - chart->addDecorativeHighlightRegion( lower_upper_energy, upper_upper_energy, - theme->timeHistoryBackgroundHighlight, - D3SpectrumDisplayDiv::HighlightRegionFill::BelowData, - upper_txt ); + if( !assertedNoSignal ) + { + const string upper_txt = SpecUtils::printCompact( upper_continuum_counts_sum, upper_ndec ); + chart->addDecorativeHighlightRegion( lower_upper_energy, upper_upper_energy, + theme->timeHistoryBackgroundHighlight, + D3SpectrumDisplayDiv::HighlightRegionFill::BelowData, + upper_txt ); + }//if( !assertedNoSignal ) // We will only put the peak on the chart if there is a result if( result && pmodel ) @@ -1599,6 +1648,8 @@ void DetectionLimitTool::update_spectrum_for_currie_result( D3SpectrumDisplayDiv { if( result->source_counts > result->decision_threshold ) { + assert( !assertedNoSignal ); + // There is enough excess counts that we would reliably detect this activity, so we will // give the activity range. string lowerstr, upperstr, nomstr; @@ -1637,6 +1688,8 @@ void DetectionLimitTool::update_spectrum_for_currie_result( D3SpectrumDisplayDiv } }else if( result->upper_limit < 0 ) { + assert( !assertedNoSignal ); + // This can happen when there are a lot fewer counts in the peak region than predicted // from the sides - since this is non-sensical, we'll just say zero. const string unitstr = useCuries ? "Ci" : "Bq"; @@ -1648,26 +1701,51 @@ void DetectionLimitTool::update_spectrum_for_currie_result( D3SpectrumDisplayDiv specific_peaks[i].setAmplitude( 0.0 ); }else { - // We will provide the upper bound on activity. - string mdastr; - if( gammas_per_bq > 0.0 ) + if( assertedNoSignal ) { - const double simple_mda = result->upper_limit / gammas_per_bq; - mdastr = PhysicalUnits::printToBestActivityUnits( simple_mda, 2, useCuries ) - + DetectorPeakResponse::det_eff_geom_type_postfix( det_geom ); + // We will provide minimum counts reliably detectable + string mdastr; + if( gammas_per_bq > 0.0 ) + { + const double simple_mda = result->detection_limit / gammas_per_bq; + mdastr = PhysicalUnits::printToBestActivityUnits( simple_mda, 2, useCuries ) + + DetectorPeakResponse::det_eff_geom_type_postfix( det_geom ); + }else + { + mdastr = SpecUtils::printCompact( result->detection_limit, 4 ) + " counts"; + } + + chart_title = "Data + peak for " + mdastr; + + generic_peak.setPeakArea( result->detection_limit ); + for( size_t i = 0; i < peaks.size(); ++i ) + { + const double area = result->detection_limit * peaks[i].counts_4pi / sum_counts_4pi; + specific_peaks[i].setAmplitude( area ); + } }else { - mdastr = SpecUtils::printCompact( result->upper_limit, 4 ) + " counts"; - } - - chart_title = "Peak for upper bound of " + mdastr + " @" + cl_str + " CL"; - - generic_peak.setPeakArea( result->upper_limit ); - for( size_t i = 0; i < peaks.size(); ++i ) - { - const double area = result->upper_limit * peaks[i].counts_4pi / sum_counts_4pi; - specific_peaks[i].setAmplitude( area ); - } + // We will provide the upper bound on activity. + string mdastr; + if( gammas_per_bq > 0.0 ) + { + const double simple_mda = result->upper_limit / gammas_per_bq; + mdastr = PhysicalUnits::printToBestActivityUnits( simple_mda, 2, useCuries ) + + DetectorPeakResponse::det_eff_geom_type_postfix( det_geom ); + }else + { + mdastr = SpecUtils::printCompact( result->upper_limit, 4 ) + " counts"; + } + + chart_title = "Peak for upper bound of " + mdastr + " @" + cl_str + " CL"; + + generic_peak.setPeakArea( result->upper_limit ); + for( size_t i = 0; i < peaks.size(); ++i ) + { + const double area = result->upper_limit * peaks[i].counts_4pi / sum_counts_4pi; + specific_peaks[i].setAmplitude( area ); + } + }//if( assertedNoSignal ) / else }//if( detected ) / else if( ....) break; @@ -1682,10 +1760,20 @@ void DetectionLimitTool::update_spectrum_for_currie_result( D3SpectrumDisplayDiv chart->setChartTitle( chart_title ); - if( specific_peaks.size() > 0 ) - pmodel->addPeaks( specific_peaks ); - else - pmodel->addPeaks( {generic_peak} ); + vector peaks = (specific_peaks.size() > 0) ? std::move(specific_peaks) : vector{generic_peak}; + + if( assertedNoSignal ) + { + for( PeakDef &p : peaks ) + { + p.continuum()->setType( PeakContinuum::OffsetType::External ); + p.continuum()->setExternalContinuum( spectrum ); + } + }//if( specIsBackground ) + + set_chi2_dof( spectrum, peaks, 0, peaks.size() ); // Compute Chi2 for peaks + + pmodel->addPeaks( peaks ); }//if( result ) }catch( std::exception &e ) { @@ -1761,10 +1849,12 @@ SimpleDialog *DetectionLimitTool::createCurrieRoiMoreInfoWindow( const SandiaDec const string confidence_level = buffer; - const double lower_lower_energy = input.spectrum->gamma_channel_lower( result.first_lower_continuum_channel ); - const double lower_upper_energy = input.spectrum->gamma_channel_lower( result.first_upper_continuum_channel ); - const double upper_lower_energy = input.spectrum->gamma_channel_upper( result.last_lower_continuum_channel ); - const double upper_upper_energy = input.spectrum->gamma_channel_upper( result.last_upper_continuum_channel ); + const bool assertedNoSignal = (result.input.num_lower_side_channels == 0); + + const double lower_upper_energy = input.spectrum->gamma_channel_lower( result.first_peak_region_channel ); + const double lower_lower_energy = assertedNoSignal ? lower_upper_energy : input.spectrum->gamma_channel_lower( result.first_lower_continuum_channel ); + const double upper_lower_energy = input.spectrum->gamma_channel_upper( result.last_peak_region_channel ); + const double upper_upper_energy = assertedNoSignal ? upper_lower_energy : input.spectrum->gamma_channel_upper( result.last_upper_continuum_channel ); // Add chart InterSpec *viewer = InterSpec::instance(); @@ -1811,7 +1901,7 @@ SimpleDialog *DetectionLimitTool::createCurrieRoiMoreInfoWindow( const SandiaDec img->decorationStyle().setCursor( Wt::Cursor::WhatsThisCursor ); HelpSystem::attachToolTipOn( img, tt, true, HelpSystem::ToolTipPosition::Right, - HelpSystem::ToolTipPrefOverride::AlwaysShow ); + HelpSystem::ToolTipPrefOverride::InstantAlways ); };//addTooltipToRow @@ -1821,6 +1911,8 @@ SimpleDialog *DetectionLimitTool::createCurrieRoiMoreInfoWindow( const SandiaDec { if( result.source_counts > result.decision_threshold ) { + assert( !assertedNoSignal ); + // There is enough excess counts that we would reliably detect this activity, so we will // give the activity range. WString obs_label, range_label; @@ -1865,6 +1957,8 @@ SimpleDialog *DetectionLimitTool::createCurrieRoiMoreInfoWindow( const SandiaDec addTooltipToRow( "The activity range estimate, to the " + confidence_level + " confidence level." ); }else if( result.upper_limit < 0 ) { + assert( !assertedNoSignal ); + // This can happen when there are a lot fewer counts in the peak region than predicted // from the sides - since this is non-sensical, we'll just say zero. const string unitstr = drf ? (useCuries ? "Ci" : "Bq") : " counts"; @@ -1920,74 +2014,82 @@ SimpleDialog *DetectionLimitTool::createCurrieRoiMoreInfoWindow( const SandiaDec }//switch( limitType ) // Add a blank row + string val; cell = table->elementAt( table->rowCount(), 0 ); WText *txt = new WText( " ", TextFormat::XHTMLText, cell ); - cell = table->elementAt( table->rowCount(), 0 ); - txt = new WText( "Lower region channels", cell ); - string val = "[" + std::to_string(result.first_lower_continuum_channel) + ", " - + std::to_string(result.last_lower_continuum_channel) + "]"; - cell = table->elementAt( table->rowCount() - 1, 1 ); - txt = new WText( val, cell ); - - - snprintf( buffer, sizeof(buffer), "The region above the peak in energy, that is being used" - " to estimate the expected continuum counts in the peak region;" - " corresponds to %.2f to %.2f keV", lower_lower_energy, lower_upper_energy ); - addTooltipToRow( buffer ); - - - cell = table->elementAt( table->rowCount(), 0 ); - txt = new WText( "Lower region counts", cell ); - val = SpecUtils::printCompact( result.lower_continuum_counts_sum, 5 ); - cell = table->elementAt( table->rowCount() - 1, 1 ); - txt = new WText( val, cell ); - addTooltipToRow( "The number of counts observed in the region below the peak region," - " that is being used to estimate expected peak-region expected counts" ); - - cell = table->elementAt( table->rowCount(), 0 ); - txt = new WText( "Upper region channels", cell ); - val = "[" + std::to_string(result.first_upper_continuum_channel) + ", " - + std::to_string(result.last_upper_continuum_channel) + "]"; - cell = table->elementAt( table->rowCount() - 1, 1 ); - txt = new WText( val, cell ); - snprintf( buffer, sizeof(buffer), "The region above the peak in energy, that is being used" - " to estimate the expected continuum counts in the peak region;" - " corresponds to %.2f to %.2f keV", upper_lower_energy, upper_upper_energy ); - addTooltipToRow( buffer ); - - cell = table->elementAt( table->rowCount(), 0 ); - txt = new WText( "Upper region counts", cell ); - val = SpecUtils::printCompact( result.upper_continuum_counts_sum, 5 ); - cell = table->elementAt( table->rowCount() - 1, 1 ); - txt = new WText( val, cell ); - addTooltipToRow( "The number of counts observed in the region above the peak region," - " that is being used to estimate expected peak-region expected counts" ); - + if( !assertedNoSignal ) + { + cell = table->elementAt( table->rowCount(), 0 ); + txt = new WText( "Lower region channels", cell ); + val = "[" + std::to_string(result.first_lower_continuum_channel) + ", " + + std::to_string(result.last_lower_continuum_channel) + "]"; + cell = table->elementAt( table->rowCount() - 1, 1 ); + txt = new WText( val, cell ); + + + snprintf( buffer, sizeof(buffer), "The region above the peak in energy, that is being used" + " to estimate the expected continuum counts in the peak region;" + " corresponds to %.2f to %.2f keV", lower_lower_energy, lower_upper_energy ); + addTooltipToRow( buffer ); + + + cell = table->elementAt( table->rowCount(), 0 ); + txt = new WText( "Lower region counts", cell ); + val = SpecUtils::printCompact( result.lower_continuum_counts_sum, 5 ); + cell = table->elementAt( table->rowCount() - 1, 1 ); + txt = new WText( val, cell ); + addTooltipToRow( "The number of counts observed in the region below the peak region," + " that is being used to estimate expected peak-region expected counts" ); + + cell = table->elementAt( table->rowCount(), 0 ); + txt = new WText( "Upper region channels", cell ); + val = "[" + std::to_string(result.first_upper_continuum_channel) + ", " + + std::to_string(result.last_upper_continuum_channel) + "]"; + cell = table->elementAt( table->rowCount() - 1, 1 ); + txt = new WText( val, cell ); + snprintf( buffer, sizeof(buffer), "The region above the peak in energy, that is being used" + " to estimate the expected continuum counts in the peak region;" + " corresponds to %.2f to %.2f keV", upper_lower_energy, upper_upper_energy ); + addTooltipToRow( buffer ); + + cell = table->elementAt( table->rowCount(), 0 ); + txt = new WText( "Upper region counts", cell ); + val = SpecUtils::printCompact( result.upper_continuum_counts_sum, 5 ); + cell = table->elementAt( table->rowCount() - 1, 1 ); + txt = new WText( val, cell ); + addTooltipToRow( "The number of counts observed in the region above the peak region," + " that is being used to estimate expected peak-region expected counts" ); + }else + { + cell = table->elementAt( table->rowCount(), 0 ); + cell->setColumnSpan( 2 ); + txt = new WText( "Using ROI area as background estimate", cell ); + }//if( !assertedNoSignal ) / else cell = table->elementAt( table->rowCount(), 0 ); - txt = new WText( "Peak area channels", cell ); - val = "[" + std::to_string(result.last_lower_continuum_channel + 1) + ", " - + std::to_string(result.first_upper_continuum_channel - 1) + "]"; + txt = new WText( assertedNoSignal ? "ROI area channels" : "Peak area channels", cell ); + val = "[" + std::to_string(result.first_peak_region_channel) + ", " + + std::to_string(result.last_peak_region_channel) + "]"; cell = table->elementAt( table->rowCount() - 1, 1 ); txt = new WText( val, cell ); - const double peak_lower_energy = input.spectrum->gamma_channel_lower( result.last_lower_continuum_channel + 1 ); - const double peak_upper_energy = input.spectrum->gamma_channel_lower( result.first_upper_continuum_channel - 1 ); + const double peak_lower_energy = input.spectrum->gamma_channel_lower( result.first_peak_region_channel ); + const double peak_upper_energy = input.spectrum->gamma_channel_upper( result.last_peak_region_channel ); snprintf( buffer, sizeof(buffer), "The region the peak is being assumed to be within;" " corresponds to %.2f to %.2f keV", peak_lower_energy, peak_upper_energy ); addTooltipToRow( buffer ); cell = table->elementAt( table->rowCount(), 0 ); - txt = new WText( "Peak region counts", cell ); + txt = new WText( assertedNoSignal ? "ROI region counts" : "Peak region counts", cell ); val = SpecUtils::printCompact( result.peak_region_counts_sum, 5 ); cell = table->elementAt( table->rowCount() - 1, 1 ); txt = new WText( val, cell ); addTooltipToRow( "The observed number of counts in the peak region" ); cell = table->elementAt( table->rowCount(), 0 ); - txt = new WText( "Peak region null est.", cell ); + txt = new WText( assertedNoSignal ? "ROI region null est.": "Peak region null est.", cell ); val = SpecUtils::printCompact( result.estimated_peak_continuum_counts, 5 ) + " ± " + SpecUtils::printCompact( result.estimated_peak_continuum_uncert, 5 ); cell = table->elementAt( table->rowCount() - 1, 1 ); @@ -1999,7 +2101,7 @@ SimpleDialog *DetectionLimitTool::createCurrieRoiMoreInfoWindow( const SandiaDec // the net signal level (instrument response) above which an observed signal may be // reliably recognized as "detected" cell = table->elementAt( table->rowCount(), 0 ); - txt = new WText( "Peak critical limit", cell ); + txt = new WText( "Peak critical limit (Lc)", cell ); if( drf && (distance >= 0.0) && (gammas_per_bq > 0.0) ) { const double decision_threshold_act = result.decision_threshold / gammas_per_bq; @@ -2023,7 +2125,7 @@ SimpleDialog *DetectionLimitTool::createCurrieRoiMoreInfoWindow( const SandiaDec // Note: I believe this quantity corresponds to Currie's "detection limit" (L_d) that // is the “true” net signal level which may be a priori expected to lead to detection. cell = table->elementAt( table->rowCount(), 0 ); - txt = new WText( "Peak detection limit", cell ); + txt = new WText( "Peak detection limit (Ld)", cell ); if( drf && (distance >= 0.0) && (gammas_per_bq > 0.0) ) { @@ -2074,14 +2176,17 @@ SimpleDialog *DetectionLimitTool::createCurrieRoiMoreInfoWindow( const SandiaDec nomstr = "< 0 counts"; }//if( drf ) / else - nomstr += " (below Lc)"; - - cell = table->elementAt( table->rowCount(), 0 ); - new WText( obs_label, cell ); - cell = table->elementAt( table->rowCount() - 1, 1 ); - new WText( nomstr, cell ); - addTooltipToRow( "The observed signal counts is less than the "critical level", Lc," - " so a detection can not be declared, but this is the excess over expected counts/activity." ); + if( !assertedNoSignal ) + { + nomstr += " (below Lc)"; + + cell = table->elementAt( table->rowCount(), 0 ); + new WText( obs_label, cell ); + cell = table->elementAt( table->rowCount() - 1, 1 ); + new WText( nomstr, cell ); + addTooltipToRow( "The observed signal counts is less than the "critical level", Lc," + " so a detection can not be declared, but this is the excess over expected counts/activity." ); + }//if( !assertedNoSignal ) }//if( result.source_counts <= result.decision_threshold ) }//case DetectionLimitTool::LimitType::Activity: diff --git a/src/ExportSpecFile.cpp b/src/ExportSpecFile.cpp index 290a986c..d6225e85 100644 --- a/src/ExportSpecFile.cpp +++ b/src/ExportSpecFile.cpp @@ -827,7 +827,7 @@ void ExportSpecFileTool::init() HelpSystem::attachToolTipOn( img, description, true, HelpSystem::ToolTipPosition::Right, - HelpSystem::ToolTipPrefOverride::AlwaysShow ); + HelpSystem::ToolTipPrefOverride::InstantAlways ); }//if( we have the description of the file ) }//if( !isMobile ) };//addFormatItem lambda diff --git a/src/GammaCountDialog.cpp b/src/GammaCountDialog.cpp index 5bf4bac4..c57dd064 100644 --- a/src/GammaCountDialog.cpp +++ b/src/GammaCountDialog.cpp @@ -230,7 +230,8 @@ void GammaCountDialog::init() m_nsigmaHelp->setHidden( true ); HelpSystem::attachToolTipOn( m_nsigmaHelp, WString::tr("gcd-tt-n-sigma"), true, - HelpSystem::ToolTipPosition::Right ); + HelpSystem::ToolTipPosition::Right, + HelpSystem::ToolTipPrefOverride::InstantAlways ); m_liveTimeScaleNote = new WText( "", XHTMLText, answers ); m_liveTimeScaleNote->addStyleClass( "GridFifthRow GridFirstCol GridJustifyCenter GridSpanThreeCol line-above LiveTimeScaleNote" ); //"" diff --git a/src/HelpSystem.cpp b/src/HelpSystem.cpp index abff8463..0c13e26b 100644 --- a/src/HelpSystem.cpp +++ b/src/HelpSystem.cpp @@ -771,6 +771,7 @@ namespace HelpSystem //the tooltip when they start typing. const bool overrideShow = (forceShowing == ToolTipPrefOverride::AlwaysShow); + const bool instantShow = (forceShowing == ToolTipPrefOverride::InstantAlways); //Create popup notifications Wt::WStringStream strm; @@ -808,8 +809,9 @@ namespace HelpSystem "method: 'flipinvert flipinvert', " "x:5} " "}," - "show: { event: '" << string(enableShowing ? "mouseenter focus" : "") << "', delay: 500 }," - "hide: { fixed: true, event: 'mouseleave focusout keypress click' }," + "show: { event: '" << (enableShowing ? (string("mouseenter") + (overrideShow ? " focus" : "")) : string("")) + << "', delay: " << string(instantShow ? "0" : "500") << " }," + "hide: { fixed: true, event: 'mouseleave focusout" << string(instantShow ? "" : " keypress click") << "' }," "style: { classes: 'qtip-rounded qtip-shadow" << string(overrideShow ? "" : " canDisableTt") << "'," "tip: {" "corner: true, " diff --git a/src/InterSpec.cpp b/src/InterSpec.cpp index b374c6bc..f2e6aedc 100644 --- a/src/InterSpec.cpp +++ b/src/InterSpec.cpp @@ -7637,7 +7637,7 @@ void InterSpec::toggleToolTip( const bool showToolTips ) //update all existing qtips if( showToolTips ) { - wApp->doJavaScript( "$('.qtip-rounded.canDisableTt').qtip('option', 'show.event', 'mouseenter focus');" ); + wApp->doJavaScript( "$('.qtip-rounded.canDisableTt').qtip('option', 'show.event', 'mouseenter');" ); }else { wApp->doJavaScript( "$('.qtip-rounded.canDisableTt').qtip('option', 'show.event', '');" ); diff --git a/src/PeakInfoDisplay.cpp b/src/PeakInfoDisplay.cpp index 2fe080bd..f1697e0b 100644 --- a/src/PeakInfoDisplay.cpp +++ b/src/PeakInfoDisplay.cpp @@ -708,7 +708,7 @@ void PeakInfoDisplay::init() dblDelagate = new ItemDelegate( m_infoView ); dblDelagate->setTextFormat( "%.0f" ); - m_infoView->setItemDelegateForColumn( PeakModel::kAmplitude, dblDelagate ); + //m_infoView->setItemDelegateForColumn( PeakModel::kAmplitude, dblDelagate ); // We'll actually return a string for peak amplitude m_infoView->setItemDelegateForColumn( PeakModel::kRoiCounts, dblDelagate ); //tweak column widths diff --git a/src/PeakModel.cpp b/src/PeakModel.cpp index 5a018db9..48e8e843 100644 --- a/src/PeakModel.cpp +++ b/src/PeakModel.cpp @@ -1921,7 +1921,38 @@ boost::any PeakModel::data( const WModelIndex &index, int role ) const }//switch( peak->type() ) case kAmplitude: - return getPeakArea(); + { + boost::any areaAny = getPeakArea(); + if( areaAny.empty() ) + return areaAny; + + const double area = boost::any_cast(areaAny); + double uncert = peak->amplitudeUncert(); + + switch( peak->type() ) + { + case PeakDef::GaussianDefined: + break; + case PeakDef::DataDefined: + uncert = -1.0; // JIC + break; + }//switch( peak->type() ) + + if( uncert <= 0.0 ) + { + char text[64]; + snprintf( text, sizeof(text), "%.2f", area ); + return WString::fromUTF8(text); + } + + // TODO: Figure out how many significant figures to show - this is kinda a guess for the moment + const int numValLeft = 1 + static_cast( std::floor( std::log10(area) ) ); //1.2 will give 1, 10.1 will give 2 + //const int numUncertLeft = 1 + static_cast( std::floor( std::log10(uncert) ) ); //0.11 will give 0, 0.011 will give -1 + + const int nsigfig = 1 + ((area > 1.0) ? std::min(std::max(3,numValLeft), 6) : 4); //we'll print out one place past decimal point + const string txt = PhysicalUnits::printValueWithUncertainty( area, uncert, nsigfig ); + return WString::fromUTF8(txt); + }//case kAmplitude: case PeakModel::kCps: { @@ -2552,7 +2583,7 @@ bool PeakModel::setData( const WModelIndex &index, switch( column ) { - case kMean: case kFwhm: case kAmplitude: + case kMean: case kIsotope: case kPhotoPeakEnergy: case kCandidateIsotopes: @@ -2562,6 +2593,21 @@ bool PeakModel::setData( const WModelIndex &index, case kUserLabel: case kPeakLineColor: break; + + case kFwhm: + case kAmplitude: + { + switch( m_sortedPeaks[row]->type() ) + { + case PeakDef::GaussianDefined: + break; + case PeakDef::DataDefined: + cerr << "PeakModel::setData(...)\n\tCant set area or FWHM for non-Gaussian peak" << endl; + return false; + }//switch( m_sortedPeaks[row]->type() ) + + break; + }//case kFwhm or kAmplitude case kCps: case kHasSkew: case kSkewAmount: case kType: case kLowerX: case kUpperX: case kRoiCounts: case kContinuumType: case kNumColumns: case kDifference: @@ -2570,7 +2616,7 @@ bool PeakModel::setData( const WModelIndex &index, return false; }//switch( section ) - double dbl_val = 0.0; + double dbl_val = 0.0, uncert_val = -1.0; WString txt_val; try @@ -2584,7 +2630,67 @@ bool PeakModel::setData( const WModelIndex &index, case kMean: case kFwhm: case kAmplitude: try { - dbl_val = std::stod( txt_val.toUTF8() ); + const string strval = txt_val.toUTF8(); + + const auto parseValWithUncert = []( const string &input, string &value, string &uncert ){ + string::size_type pos = input.find( "\xC2\xB1" ); + if( pos == string::npos ) + pos = input.find( "+-" ); + if( pos == string::npos ) + pos = input.find( "-+" ); + if( pos == string::npos ) + { + value = input; + SpecUtils::trim(value); + return; + } + + value = input.substr(0, pos); + uncert = input.substr(pos + 2); //All "+-" strings are two bytes long + SpecUtils::trim(value); + SpecUtils::trim(uncert); + }; + + string valstr, uncertstr; + parseValWithUncert( strval, valstr, uncertstr ); + + dbl_val = std::stod( valstr ); + if( !uncertstr.empty() ) + uncert_val = std::stod( uncertstr ); + + // There could be some rounding in the string representation of the uncertainty, or the + // area so lets avoid this if the user hasnt changed that part of it + const boost::any prevData = data( index, role ); + if( !prevData.empty() ) + { + WString prevDataWstr = boost::any_cast( prevData ); + + assert( row < m_sortedPeaks.size() ); + const PeakShrdPtr &peak = m_sortedPeaks[row]; + + string prevValStr, prevUncertStr; + parseValWithUncert( prevDataWstr.toUTF8(), prevValStr, prevUncertStr ); + + if( prevValStr == valstr ) + { + if( column == kMean ) + dbl_val = peak->mean(); + else if( column == kFwhm ) + dbl_val = peak->fwhm(); + else if( column == kAmplitude && (peak->type() == PeakDef::GaussianDefined) ) + dbl_val = peak->amplitude(); + }//if( prevValStr == valstr ) + + if( (uncert_val > 0.0) && (prevUncertStr == uncertstr) ) + { + if( column == kMean ) + uncert_val = peak->meanUncert(); + else if( column == kFwhm ) + uncert_val = 2.3548201 * peak->sigmaUncert(); + else if( column == kAmplitude && (peak->type() == PeakDef::GaussianDefined) ) + uncert_val = peak->amplitudeUncert(); + }//if( prevValStr == valstr ) + }//if( prevData, so we can potentially avoid rounding values ) }catch(...) { cerr << "PeakModel::setData(...)\n\tUnable to convert '" << txt_val @@ -2611,6 +2717,8 @@ bool PeakModel::setData( const WModelIndex &index, { case PeakDef::GaussianDefined: new_peak.setMean( dbl_val ); + if( uncert_val > 0.0 ) + new_peak.setMeanUncert( uncert_val ); break; case PeakDef::DataDefined: @@ -2624,6 +2732,8 @@ bool PeakModel::setData( const WModelIndex &index, { case PeakDef::GaussianDefined: new_peak.setSigma( dbl_val / 2.3548201 ); + if( uncert_val > 0.0 ) + new_peak.setSigmaUncert( uncert_val / 2.3548201 ); break; case PeakDef::DataDefined: @@ -2637,6 +2747,8 @@ bool PeakModel::setData( const WModelIndex &index, { case PeakDef::GaussianDefined: new_peak.setAmplitude( dbl_val ); + if( uncert_val > 0.0 ) + new_peak.setAmplitudeUncert( uncert_val ); break; case PeakDef::DataDefined: @@ -2962,10 +3074,11 @@ bool PeakModel::setData( const WModelIndex &index, if( column != kIsotope && column != kPhotoPeakEnergy - && column != kMean ) + && column != kMean + && column != kAmplitude ) { dataChanged().emit( index, index ); - }else if( column == kPhotoPeakEnergy ) + }else if( (column == kPhotoPeakEnergy) || (column == kAmplitude) ) { if( changedFit ) dataChanged().emit( PeakModel::index(row, kIsotope), PeakModel::index(row, kNumColumns-1) ); //just update whole row, jic @@ -2999,13 +3112,30 @@ WFlags PeakModel::flags( const WModelIndex &index ) const { switch( index.column() ) { - case kMean: case kFwhm: case kAmplitude: case kUserLabel: + case kMean: case kUserLabel: case kIsotope: case kPhotoPeakEnergy: #if( ALLOW_PEAK_COLOR_DELEGATE ) case kPeakLineColor: #endif return ItemIsEditable | ItemIsSelectable; //ItemIsSelectabl + case kFwhm: case kAmplitude: + { + const int row = index.row(); + if( row < static_cast(m_sortedPeaks.size()) ) + { + switch( m_sortedPeaks[row]->type() ) + { + case PeakDef::GaussianDefined: + return ItemIsEditable | ItemIsSelectable; + case PeakDef::DataDefined: + return ItemIsSelectable; + }//switch( peak type ) + }//if( a valid row of a peak ) + + return ItemIsEditable | ItemIsSelectable; + }//case kFwhm: case kAmplitude: + case kUseForShieldingSourceFit: case kUseForCalibration: case kUseForManualRelEff: diff --git a/src/RelActCalcAuto.cpp b/src/RelActCalcAuto.cpp index efed4303..2844bbac 100644 --- a/src/RelActCalcAuto.cpp +++ b/src/RelActCalcAuto.cpp @@ -4806,8 +4806,6 @@ void RelActAutoSolution::print_html_report( std::ostream &out ) const const string d3_js = load_file_contents( "d3.v3.min.js" ); const string spectrum_chart_d3_js = load_file_contents( "SpectrumChartD3.js" ); const string spectrum_chart_d3_css = load_file_contents( "SpectrumChartD3.css" ); - //const string spectrum_chart_d3_standalone_css = load_file_contents( "SpectrumChartD3StandAlone.css" ); - SpecUtils::ireplace_all( html, "\\;", ";" ); diff --git a/src/ShieldingSourceDisplay.cpp b/src/ShieldingSourceDisplay.cpp index 52dde518..0b23e58d 100644 --- a/src/ShieldingSourceDisplay.cpp +++ b/src/ShieldingSourceDisplay.cpp @@ -3665,6 +3665,15 @@ pair, ROOT::Minuit2::Mn }//case ShieldingSourceFitCalc::ModelSourceType::Trace: }//switch( m_sourceModel->sourceType(ison) ) + // If we are fitting activity or age, we can possibly get into a situation where the + // values have become NaN - if this is the case, lets put in a number, to hopefully + // help get out of this badness + if( srcdef.fitAge && (IsInf(srcdef.age) || IsNan(srcdef.age)) ) + srcdef.age = PeakDef::defaultDecayTime( nuclide, nullptr ); + + if( srcdef.fitActivity && (IsInf(srcdef.activity) || IsNan(srcdef.activity)) ) + srcdef.activity = 1.0E-6 * PhysicalUnits::curie; + src_definitions.push_back( srcdef ); }//for( const SandiaDecay::Nuclide *nuc : nuclides ) diff --git a/src/ShowRiidInstrumentsAna.cpp b/src/ShowRiidInstrumentsAna.cpp index 4d62bb79..2bb88987 100644 --- a/src/ShowRiidInstrumentsAna.cpp +++ b/src/ShowRiidInstrumentsAna.cpp @@ -292,7 +292,7 @@ class AnaResultDisplay : public WContainerWidget + ""; if( res.distance_ > 0.0 ) result += "" + WString::tr("Distance").toUTF8() - + "" + PhysicalUnits::printToBestLengthUnits(0.1*res.distance_) + ""; + + "" + PhysicalUnits::printToBestLengthUnits(res.distance_ * PhysicalUnits::mm,4) + ""; if( res.activity_ > 0.0 ) { const bool useBq = InterSpecUser::preferenceValue( "DisplayBecquerel", InterSpec::instance() ); diff --git a/target/wxWidgets/CMakeLists.txt b/target/wxWidgets/CMakeLists.txt index 06eddf6b..640836f9 100644 --- a/target/wxWidgets/CMakeLists.txt +++ b/target/wxWidgets/CMakeLists.txt @@ -184,7 +184,6 @@ install( ${CMAKE_CURRENT_SOURCE_DIR}/../../external_libs/SpecUtils/d3_resources/d3.v3.min.js ${CMAKE_CURRENT_SOURCE_DIR}/../../external_libs/SpecUtils/d3_resources/SpectrumChartD3.css ${CMAKE_CURRENT_SOURCE_DIR}/../../external_libs/SpecUtils/d3_resources/SpectrumChartD3.js - ${CMAKE_CURRENT_SOURCE_DIR}/../../external_libs/SpecUtils/d3_resources/SpectrumChartD3StandAlone.css DESTINATION "${RESOURCE_DEST_DIR}/InterSpec_resources" )