diff --git a/MantidQt/CustomInterfaces/CMakeLists.txt b/MantidQt/CustomInterfaces/CMakeLists.txt index 020e27b72395279eee7b1ab89d0d5f55120835da..ce164bd03c6ba9532d70637ec95a4c9bf3e919f7 100644 --- a/MantidQt/CustomInterfaces/CMakeLists.txt +++ b/MantidQt/CustomInterfaces/CMakeLists.txt @@ -85,7 +85,9 @@ set ( SRC_FILES src/Reflectometry/ReflNexusMeasurementItemSource.cpp src/Reflectometry/ReflSearchModel.cpp src/Reflectometry/ReflTableSchema.cpp - src/Reflectometry/TransferResults.cpp + src/Reflectometry/TransferResults.cpp + src/SANSBackgroundCorrectionSettings.cpp + src/SANSBackgroundCorrectionWidget.cpp src/SANSAddFiles.cpp src/SANSConstants.cpp src/SANSDiagnostics.cpp @@ -218,7 +220,9 @@ set ( INC_FILES inc/MantidQtCustomInterfaces/Reflectometry/ReflTableSchema.h inc/MantidQtCustomInterfaces/Reflectometry/ReflTransferStrategy.h inc/MantidQtCustomInterfaces/Reflectometry/ReflVectorString.h - inc/MantidQtCustomInterfaces/Reflectometry/TransferResults.h + inc/MantidQtCustomInterfaces/Reflectometry/TransferResults.h + inc/MantidQtCustomInterfaces/SANSBackgroundCorrectionSettings.h + inc/MantidQtCustomInterfaces/SANSBackgroundCorrectionWidget.h inc/MantidQtCustomInterfaces/SANSAddFiles.h inc/MantidQtCustomInterfaces/SANSConstants.h inc/MantidQtCustomInterfaces/SANSDiagnostics.h @@ -330,6 +334,7 @@ set ( MOC_FILES inc/MantidQtCustomInterfaces/Background.h inc/MantidQtCustomInterfaces/Reflectometry/QtReflMainView.h inc/MantidQtCustomInterfaces/Reflectometry/QtReflOptionsDialog.h inc/MantidQtCustomInterfaces/SampleTransmission.h + inc/MantidQtCustomInterfaces/SANSBackgroundCorrectionWidget.h inc/MantidQtCustomInterfaces/SANSAddFiles.h inc/MantidQtCustomInterfaces/SANSPlotSpecial.h inc/MantidQtCustomInterfaces/SANSRunWindow.h @@ -396,6 +401,7 @@ set ( UI_FILES inc/MantidQtCustomInterfaces/DataComparison.ui inc/MantidQtCustomInterfaces/Reflectometry/ReflOptionsDialog.ui inc/MantidQtCustomInterfaces/Reflectometry/ReflWindow.ui inc/MantidQtCustomInterfaces/SampleTransmission.ui + inc/MantidQtCustomInterfaces/SANSBackgroundCorrectionWidget.ui inc/MantidQtCustomInterfaces/SANSPlotSpecial.ui inc/MantidQtCustomInterfaces/SANSRunWindow.ui inc/MantidQtCustomInterfaces/SANSEventSlicing.ui diff --git a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSBackgroundCorrectionSettings.h b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSBackgroundCorrectionSettings.h new file mode 100644 index 0000000000000000000000000000000000000000..0614b447a70fa43feb53019c0e3a41882796fe7b --- /dev/null +++ b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSBackgroundCorrectionSettings.h @@ -0,0 +1,59 @@ +#ifndef MANTIDQT_CUSTOMINTERFACES_SANSBACKGROUNDCORRECTIONSETTINGS_H_ +#define MANTIDQT_CUSTOMINTERFACES_SANSBACKGROUNDCORRECTIONSETTINGS_H_ + +#include "MantidKernel/System.h" +#include "MantidQtCustomInterfaces/DllConfig.h" + +#include <QString> +#include <boost/optional.hpp> + +namespace MantidQt { +namespace CustomInterfaces { +/** SANSBackgroundCorrectionSettings: A simple communication class between for +the SANSBackgroundCorrectionWidget + +Copyright © 2015 ISIS Rutherford Appleton Laboratory, NScD Oak Ridge +National Laboratory & European Spallation Source + +This file is part of Mantid. + +Mantid is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. + +Mantid is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. + +File change history is stored at: <https://github.com/mantidproject/mantid> +Code Documentation is available at: <http://doxygen.mantidproject.org> +*/ +class MANTIDQT_CUSTOMINTERFACES_DLL SANSBackgroundCorrectionSettings { +public: + SANSBackgroundCorrectionSettings(QString runNumber, bool useMean, bool useMon, + QString monNumber); + + bool hasValidSettings(); + + QString getRunNumber() const; + QString getMonNumber() const; + bool getUseMean() const; + bool getUseMon() const; + +private: + boost::optional<bool> m_hasValidSettings; + const QString m_runNumber; + const bool m_useMean; + const bool m_useMon; + const QString m_monNumber; +}; + +} // namespace CustomInterfaces +} // namespace MantidQt + +#endif /* MANTIDQT_CUSTOMINTERFACES_SANSBACKGROUNDCORRECTIONSETTINGS_H_ */ diff --git a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSBackgroundCorrectionWidget.h b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSBackgroundCorrectionWidget.h new file mode 100644 index 0000000000000000000000000000000000000000..bf1244eafd04cf6d32d66be8dc41dacd5601cdab --- /dev/null +++ b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSBackgroundCorrectionWidget.h @@ -0,0 +1,78 @@ +#ifndef MANTIDQT_CUSTOMINTERFACES_SANSBACKGROUNDCORRECTIONWIDGET_H_ +#define MANTIDQT_CUSTOMINTERFACES_SANSBACKGROUNDCORRECTIONWIDGET_H_ + +#include "MantidKernel/System.h" +#include "MantidQtCustomInterfaces/DllConfig.h" +#include "ui_SANSBackgroundCorrectionWidget.h" + +#include <QWidget> +#include <string> + +namespace MantidQt { +namespace CustomInterfaces { +class SANSBackgroundCorrectionSettings; + +/** SANSBackgroundCorrectionWidget: Widget for background correction of SANS +experiments. + +Copyright © 2015 ISIS Rutherford Appleton Laboratory, NScD Oak Ridge +National Laboratory & European Spallation Source + +This file is part of Mantid. + +Mantid is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. + +Mantid is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. + +File change history is stored at: <https://github.com/mantidproject/mantid> +Code Documentation is available at: <http://doxygen.mantidproject.org> +*/ +class MANTIDQT_CUSTOMINTERFACES_DLL SANSBackgroundCorrectionWidget + : public QWidget { + Q_OBJECT +public: + SANSBackgroundCorrectionWidget(QWidget *parent = 0); + + void + setDarkRunSettingForTimeDetectors(SANSBackgroundCorrectionSettings setting); + SANSBackgroundCorrectionSettings getDarkRunSettingForTimeDetectors(); + void + setDarkRunSettingForUampDetectors(SANSBackgroundCorrectionSettings setting); + SANSBackgroundCorrectionSettings getDarkRunSettingForUampDetectors(); + + void + setDarkRunSettingForTimeMonitors(SANSBackgroundCorrectionSettings setting); + SANSBackgroundCorrectionSettings getDarkRunSettingForTimeMonitors(); + void + setDarkRunSettingForUampMonitors(SANSBackgroundCorrectionSettings setting); + SANSBackgroundCorrectionSettings getDarkRunSettingForUampMonitors(); + + void resetEntries(); + +private slots: + void handleTimeDetectorsOnOff(int state); + void handleUampDetectorsOnOff(int state); + void handleTimeMonitorsOnOff(int state); + void handleUampMonitorsOnOff(int state); + +private: + /// Setup the connections + void setupConnections(); + + /// UI form + Ui::SANSBackgroundCorrectionWidget m_ui; +}; + +} // namespace CustomInterfaces +} // namespace MantidQt + +#endif /* MANTIDQT_CUSTOMINTERFACES_SANSBACKGROUNDCORRECTIONWIDGET_H_ */ diff --git a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSBackgroundCorrectionWidget.ui b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSBackgroundCorrectionWidget.ui new file mode 100644 index 0000000000000000000000000000000000000000..4d030c764fe43985c524575032848031d3802db3 --- /dev/null +++ b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSBackgroundCorrectionWidget.ui @@ -0,0 +1,235 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>SANSBackgroundCorrectionWidget</class> + <widget class="QWidget" name="SANSBackgroundCorrectionWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>441</width> + <height>124</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>40</height> + </size> + </property> + <property name="windowTitle"> + <string>ModeControlWidget</string> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <property name="leftMargin"> + <number>1</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>1</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item row="0" column="0"> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0"> + <widget class="QGroupBox" name="bckgnd_cor_group_box"> + <property name="title"> + <string>Background Subtraction</string> + </property> + <layout class="QGridLayout" name="gridLayout_5"> + <item row="4" column="0"> + <widget class="QLabel" name="bckgnd_cor_uamp_label"> + <property name="text"> + <string>Uamp</string> + </property> + </widget> + </item> + <item row="0" column="9"> + <widget class="QLabel" name="bckgnd_cor_monitors_label"> + <property name="text"> + <string>Monitors</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="bckgnd_cor_time_label"> + <property name="text"> + <string>Time</string> + </property> + </widget> + </item> + <item row="4" column="2"> + <widget class="QCheckBox" name="bckgnd_cor_det_uamp_use_check_box"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="4" column="7"> + <widget class="QCheckBox" name="bckgnd_cor_mon_uamp_use_check_box"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QLabel" name="bckgnd_cor_detector_label"> + <property name="text"> + <string>Detectors</string> + </property> + </widget> + </item> + <item row="1" column="5"> + <widget class="QLabel" name="bckgnd_cor_det_mean_label"> + <property name="text"> + <string>Mean</string> + </property> + </widget> + </item> + <item row="1" column="7"> + <widget class="QLabel" name="bckgnd_cor_mon_use_label"> + <property name="text"> + <string>Use</string> + </property> + </widget> + </item> + <item row="3" column="10"> + <widget class="QLineEdit" name="bckgnd_cor_mon_time_mon_num_line_edit"/> + </item> + <item row="3" column="9"> + <widget class="QCheckBox" name="bckgnd_cor_mon_mean_check_box"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="4" column="10"> + <widget class="QLineEdit" name="bckgnd_cor_mon_uamp_mon_num_line_edit"/> + </item> + <item row="4" column="8"> + <widget class="QLineEdit" name="bckgnd_cor_mon_uamp_run_line_edit"/> + </item> + <item row="3" column="7"> + <widget class="QCheckBox" name="bckgnd_cor_mon_time_use_check_box"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="3" column="2"> + <widget class="QCheckBox" name="bckgnd_cor_det_time_use_check_box"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="3" column="8"> + <widget class="QLineEdit" name="bckgnd_cor_mon_time_run_line_edit"/> + </item> + <item row="4" column="3" colspan="2"> + <widget class="QLineEdit" name="bckgnd_cor_det_uamp_run_line_edit"/> + </item> + <item row="3" column="5"> + <widget class="QCheckBox" name="bckgnd_cor_det_mean_check_box"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="3" column="3" colspan="2"> + <widget class="QLineEdit" name="bckgnd_cor_det_time_run_line_edit"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="1" column="9"> + <widget class="QLabel" name="bckgnd_cor_mon_mean_label"> + <property name="text"> + <string>Mean</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLabel" name="bckgnd_cor_det_use_label"> + <property name="text"> + <string>Use</string> + </property> + </widget> + </item> + <item row="1" column="8"> + <widget class="QLabel" name="bckgnd_cor_mon_run_label"> + <property name="text"> + <string>Run</string> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QLabel" name="bckgnd_cor_det_run_label"> + <property name="text"> + <string>Run</string> + </property> + </widget> + </item> + <item row="1" column="10"> + <widget class="QLabel" name="bckgnd_cor_mon_mon_num_line_edit"> + <property name="text"> + <string>Mon Num</string> + </property> + </widget> + </item> + <item row="1" column="6" rowspan="4"> + <widget class="QFrame" name="frame"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>QFrame::VLine</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <property name="lineWidth"> + <number>4</number> + </property> + </widget> + </item> + <item row="1" column="1" rowspan="4"> + <widget class="QFrame" name="frame_2"> + <property name="frameShape"> + <enum>QFrame::VLine</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <property name="lineWidth"> + <number>4</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <layoutdefault spacing="6" margin="11"/> + <resources/> + <connections/> +</ui> diff --git a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSConstants.h b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSConstants.h index fb4d8c849507594507225e8d696385b96365b02c..d30875cdc8b39bed82cfab28910d543269e471e0 100644 --- a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSConstants.h +++ b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSConstants.h @@ -15,21 +15,20 @@ public: ~SANSConstants(); // Python related - QString getPythonSuccessKeyword(); - QString getPythonEmptyKeyword(); - QString getPythonTrueKeyword(); - QString getPythonFalseKeyword(); + static QString getPythonSuccessKeyword(); + static QString getPythonEmptyKeyword(); + static QString getPythonTrueKeyword(); + static QString getPythonFalseKeyword(); - QString getQResolutionH1ToolTipText(); - QString getQResolutionH2ToolTipText(); - QString getQResolutionA1ToolTipText(); - QString getQResolutionA2ToolTipText(); + static QString getQResolutionH1ToolTipText(); + static QString getQResolutionH2ToolTipText(); + static QString getQResolutionA1ToolTipText(); + static QString getQResolutionA2ToolTipText(); // Input related - double getMaxDoubleValue(); - int getMaxIntValue(); - int getDecimals(); - + static double getMaxDoubleValue(); + static int getMaxIntValue(); + static int getDecimals(); }; } diff --git a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.h b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.h index 4ee743ad7299efdc90821e6f6ab4570a715bc942..5a13fde2591452eede9b66290203b0c1b95569e6 100644 --- a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.h +++ b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.h @@ -4,21 +4,21 @@ //---------------------- // Includes //---------------------- -#include "ui_SANSRunWindow.h" #include "MantidQtAPI/UserSubWindow.h" #include "MantidQtCustomInterfaces/SANSAddFiles.h" -#include "MantidQtMantidWidgets/SaveWorkspaces.h" +#include "MantidQtCustomInterfaces/SANSConstants.h" #include "MantidQtCustomInterfaces/SANSDiagnostics.h" #include "MantidQtCustomInterfaces/SANSPlotSpecial.h" -#include "MantidQtCustomInterfaces/SANSConstants.h" +#include "MantidQtMantidWidgets/SaveWorkspaces.h" +#include "ui_SANSRunWindow.h" -#include <QHash> -#include <QSettings> -#include <QStringList> -#include <Poco/NObserver.h> #include "MantidAPI/AnalysisDataService.h" #include "MantidAPI/MatrixWorkspace_fwd.h" #include "MantidKernel/ConfigService.h" +#include <Poco/NObserver.h> +#include <QHash> +#include <QSettings> +#include <QStringList> #include <vector> namespace Mantid { @@ -493,11 +493,26 @@ private: bool w1W2Disabled); /// Initialize the QResolution settings void initQResolutionSettings(); + + /// Gets the BackgroundCorrection settings + void retrieveBackgroundCorrection(); + /// Get Background runner + SANSBackgroundCorrectionSettings retrieveBackgroundCorrectionSetting(bool isTime, bool isMon); + /// Initialize the background correction + void initializeBackgroundCorrection(); + /// Sets the BackgroundCorrection settings + void writeBackgroundCorrectionToPythonScript(QString &pythonCode); + /// Generic addition of background correction to python script + void addBackgroundCorrectionToPythonScript( + QString &pythonCode, + MantidQt::CustomInterfaces::SANSBackgroundCorrectionSettings setting, + bool isTimeBased); /// Check if the user file has a valid user file extension bool hasUserFileValidFileExtension(); /// Check if the user file is valid bool isValidUserFile(); + UserSubWindow *slicingWindow; }; } diff --git a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.ui b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.ui index 29d108c1603286b44894a2d30d8cb85ae67c1985..4f3d713890fad8800c2252e69aaf42b08bc42b7a 100644 --- a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.ui +++ b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>950</width> - <height>605</height> + <height>632</height> </rect> </property> <property name="sizePolicy"> @@ -1866,19 +1866,6 @@ intermediate ranges are there for comparison</string> </layout> </widget> </item> - <item row="7" column="1"> - <spacer name="verticalSpacer_8"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> <item row="2" column="1" rowspan="3"> <layout class="QVBoxLayout" name="verticalLayout_11"> <item> @@ -2525,6 +2512,9 @@ intermediate ranges are there for comparison</string> </item> </layout> </item> + <item row="7" column="1"> + <widget class="MantidQt::CustomInterfaces::SANSBackgroundCorrectionWidget" name="sansBackgroundCorrectionWidget" native="true"/> + </item> </layout> </widget> <widget class="QWidget" name="tab_4"> @@ -2704,7 +2694,7 @@ intermediate ranges are there for comparison</string> </sizepolicy> </property> <property name="currentIndex"> - <number>1</number> + <number>0</number> </property> <widget class="QWidget" name="page_3"> <layout class="QGridLayout" name="gridLayout_6"> @@ -3510,13 +3500,13 @@ p, li { white-space: pre-wrap; } </widget> </item> </layout> + <zorder>label_161</zorder> <zorder>rear_beam_y</zorder> <zorder>rear_radio</zorder> <zorder>front_beam_y</zorder> <zorder>front_beam_x</zorder> <zorder>front_radio</zorder> <zorder>rear_beam_x</zorder> - <zorder>label_16</zorder> </widget> </item> <item> @@ -4659,6 +4649,12 @@ p, li { white-space: pre-wrap; } <extends>QWidget</extends> <header>MantidQtMantidWidgets/MessageDisplay.h</header> </customwidget> + <customwidget> + <class>MantidQt::CustomInterfaces::SANSBackgroundCorrectionWidget</class> + <extends>QWidget</extends> + <header>MantidQtCustomInterfaces/SANSBackgroundCorrectionWidget.h</header> + <container>1</container> + </customwidget> </customwidgets> <tabstops> <tabstop>rad_min</tabstop> diff --git a/MantidQt/CustomInterfaces/src/SANSBackgroundCorrectionSettings.cpp b/MantidQt/CustomInterfaces/src/SANSBackgroundCorrectionSettings.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0458c653e9edd2db74fd662bf0051b69037595a7 --- /dev/null +++ b/MantidQt/CustomInterfaces/src/SANSBackgroundCorrectionSettings.cpp @@ -0,0 +1,57 @@ +#include "MantidQtCustomInterfaces/SANSBackgroundCorrectionSettings.h" + +namespace MantidQt { +namespace CustomInterfaces { +SANSBackgroundCorrectionSettings::SANSBackgroundCorrectionSettings( + QString runNumber, bool useMean, bool useMon, QString monNumber) + : m_runNumber(runNumber), m_useMean(useMean), m_useMon(useMon), + m_monNumber(monNumber) { + hasValidSettings(); +} + +/** + * Check if the settings stored in the object are valid + * @returns true if they are valid, else false + */ +bool SANSBackgroundCorrectionSettings::hasValidSettings() { + if (!m_hasValidSettings) { + // The run number must not be empty + m_hasValidSettings = m_runNumber.isEmpty() ? false : true; + } + + return m_hasValidSettings.get(); +} + +/** + * Get the run number + * @returns a run number or an empty string + */ +QString SANSBackgroundCorrectionSettings::getRunNumber() const { + return m_hasValidSettings ? m_runNumber : QString(); +} + +/** +* Get a string list with monitor numbers +* @returns a run number or an empty string +*/ +QString SANSBackgroundCorrectionSettings::getMonNumber() const { + return m_hasValidSettings ? m_monNumber : QString(); +} + +/** +* Get the setting if mean is to be used +* @returns the setting or default to false +*/ +bool SANSBackgroundCorrectionSettings::getUseMean() const { + return m_hasValidSettings ? m_useMean : false; +} + +/** +* Get the setting if monitors or detectors are to be used +* @returns the setting or default to false +*/ +bool SANSBackgroundCorrectionSettings::getUseMon() const { + return m_hasValidSettings ? m_useMon : false; +} +} +} diff --git a/MantidQt/CustomInterfaces/src/SANSBackgroundCorrectionWidget.cpp b/MantidQt/CustomInterfaces/src/SANSBackgroundCorrectionWidget.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0d3161ef3a19bd5cd646d4eb4415e834d2b90235 --- /dev/null +++ b/MantidQt/CustomInterfaces/src/SANSBackgroundCorrectionWidget.cpp @@ -0,0 +1,266 @@ +#include "MantidKernel/Logger.h" +#include "MantidQtCustomInterfaces/SANSBackgroundCorrectionSettings.h" +#include "MantidQtCustomInterfaces/SANSBackgroundCorrectionWidget.h" +namespace { +bool convertQtInt(int state) { return state == 2 ? true : false; } +} + +namespace { +/// static logger for main window +Mantid::Kernel::Logger g_log("SANSBackgroundCorrectionWidget"); + +bool hasRunNumber( + MantidQt::CustomInterfaces::SANSBackgroundCorrectionSettings setting) { + auto hasNumber = true; + if (setting.getRunNumber().isEmpty()) { + hasNumber = false; + } + return hasNumber; +} +} + +namespace MantidQt { +namespace CustomInterfaces { +SANSBackgroundCorrectionWidget::SANSBackgroundCorrectionWidget(QWidget *parent) + : QWidget(parent) { + m_ui.setupUi(this); + + // Disable all inputs initially + handleTimeDetectorsOnOff(0); + handleTimeMonitorsOnOff(0); + handleUampDetectorsOnOff(0); + handleUampMonitorsOnOff(0); + + // Setup signal slot connections + setupConnections(); +} + +// -------- SETTERS + +/** + * Set the dark run settings for time-based subtractions for detectors + * @param setting: the dark run settings for time-based subtractions, ie when we + * want + */ +void SANSBackgroundCorrectionWidget::setDarkRunSettingForTimeDetectors( + SANSBackgroundCorrectionSettings setting) { + + if (!hasRunNumber(setting)) { + return; + } + + if (setting.getUseMon()) { + g_log.warning("SANSBackgroundCorrectionWidget: Trying to pass a background " + "correction " + "setting of a monitor to a detector display."); + return; + } + + m_ui.bckgnd_cor_det_time_use_check_box->setChecked(true); + m_ui.bckgnd_cor_det_time_run_line_edit->setText(setting.getRunNumber()); + m_ui.bckgnd_cor_det_mean_check_box->setChecked(setting.getUseMean()); +} + +/** +* Set the dark run settings for time-based subtractions for monitors +* @param setting: the dark run settings for time-based subtractions, ie when we +* want +*/ +void SANSBackgroundCorrectionWidget::setDarkRunSettingForTimeMonitors( + SANSBackgroundCorrectionSettings setting) { + if (!hasRunNumber(setting)) { + return; + } + + if (!setting.getUseMon()) { + g_log.warning("SANSBackgroundCorrectionWidget: Trying to pass a background " + "correction " + "setting of a detector to a monitor display."); + return; + } + + m_ui.bckgnd_cor_mon_time_use_check_box->setChecked(true); + m_ui.bckgnd_cor_mon_time_run_line_edit->setText(setting.getRunNumber()); + m_ui.bckgnd_cor_mon_mean_check_box->setChecked(setting.getUseMean()); + m_ui.bckgnd_cor_mon_time_mon_num_line_edit->setText(setting.getMonNumber()); +} + + +/** +* Set the dark run settings for uamp-based subtractions for detectors +* @param setting: the dark run settings for uamp-based subtractions, ie when we +* want +*/ +void SANSBackgroundCorrectionWidget::setDarkRunSettingForUampDetectors( + SANSBackgroundCorrectionSettings setting) { + if (!hasRunNumber(setting)) { + return; + } + + if (setting.getUseMon()) { + g_log.warning("SANSBackgroundCorrectionWidget: Trying to pass a background " + "correction " + "setting of a monitor to a detector display."); + return; + } + m_ui.bckgnd_cor_det_uamp_use_check_box->setChecked(true); + m_ui.bckgnd_cor_det_uamp_run_line_edit->setText(setting.getRunNumber()); +} + +/** +* Set the dark run settings for uamp-based subtractions for detectors +* @param setting: the dark run settings for uamp-based subtractions, ie when we +* want +*/ +void SANSBackgroundCorrectionWidget::setDarkRunSettingForUampMonitors( + SANSBackgroundCorrectionSettings setting) { + if (!hasRunNumber(setting)) { + return; + } + + if (!setting.getUseMon()) { + g_log.warning("SANSBackgroundCorrectionWidget: Trying to pass a background " + "correction " + "setting of a detector to a monitor display."); + return; + } + + m_ui.bckgnd_cor_mon_uamp_use_check_box->setChecked(true); + m_ui.bckgnd_cor_mon_uamp_run_line_edit->setText(setting.getRunNumber()); + m_ui.bckgnd_cor_mon_uamp_mon_num_line_edit->setText(setting.getMonNumber()); +} + +//---------------- GETTERS + +/** +* Get the dark run settings for time-based subtractions for detectors +* @returns the dark run settings for time-based subtractions +*/ +SANSBackgroundCorrectionSettings +SANSBackgroundCorrectionWidget::getDarkRunSettingForTimeDetectors() { + QString runNumber(""); + bool useMean = false; + bool useMon = false; + QString monNumber(""); + + if (m_ui.bckgnd_cor_det_time_use_check_box->isChecked()) { + runNumber = m_ui.bckgnd_cor_det_time_run_line_edit->text(); + useMean = m_ui.bckgnd_cor_det_mean_check_box->isChecked(); + } + return SANSBackgroundCorrectionSettings(runNumber, useMean, useMon, + monNumber); +} + + +/** +* Get the dark run settings for time-based subtractions for detectors +* @returns the dark run settings for time-based subtractions +*/ +SANSBackgroundCorrectionSettings +SANSBackgroundCorrectionWidget::getDarkRunSettingForTimeMonitors() { + QString runNumber(""); + bool useMean = false; + bool useMon = true; + QString monNumber(""); + + if (m_ui.bckgnd_cor_mon_time_use_check_box->isChecked()) { + runNumber = m_ui.bckgnd_cor_mon_time_run_line_edit->text(); + useMean = m_ui.bckgnd_cor_mon_mean_check_box->isChecked(); + monNumber = m_ui.bckgnd_cor_mon_time_mon_num_line_edit->text(); + } + return SANSBackgroundCorrectionSettings(runNumber, useMean, useMon, + monNumber); +} + +/** +* Get the dark run settings for uamp-based subtractions for detectors +* @returns the dark run settings for uamp-based subtractions +*/ +SANSBackgroundCorrectionSettings +SANSBackgroundCorrectionWidget::getDarkRunSettingForUampDetectors() { + QString runNumber(""); + bool useMean = false; + bool useMon = false; + QString monNumber(""); + + if (m_ui.bckgnd_cor_det_uamp_use_check_box->isChecked()) { + runNumber = m_ui.bckgnd_cor_det_uamp_run_line_edit->text(); + } + return SANSBackgroundCorrectionSettings(runNumber, useMean, useMon, + monNumber); +} + +/** +* Get the dark run settings for uamp-based subtractions for detectors +* @returns the dark run settings for uamp-based subtractions +*/ +SANSBackgroundCorrectionSettings +SANSBackgroundCorrectionWidget::getDarkRunSettingForUampMonitors() { + QString runNumber(""); + bool useMean = false; + bool useMon = true; + QString monNumber(""); + + if (m_ui.bckgnd_cor_mon_uamp_use_check_box->isChecked()) { + runNumber = m_ui.bckgnd_cor_mon_uamp_run_line_edit->text(); + monNumber = m_ui.bckgnd_cor_mon_uamp_mon_num_line_edit->text(); + } + return SANSBackgroundCorrectionSettings(runNumber, useMean, useMon, + monNumber); +} + +void SANSBackgroundCorrectionWidget::setupConnections() { + QObject::connect(m_ui.bckgnd_cor_det_time_use_check_box, + SIGNAL(stateChanged(int)), this, + SLOT(handleTimeDetectorsOnOff(int))); + QObject::connect(m_ui.bckgnd_cor_det_uamp_use_check_box, + SIGNAL(stateChanged(int)), this, + SLOT(handleUampDetectorsOnOff(int))); + + QObject::connect(m_ui.bckgnd_cor_mon_time_use_check_box, + SIGNAL(stateChanged(int)), this, + SLOT(handleTimeMonitorsOnOff(int))); + QObject::connect(m_ui.bckgnd_cor_mon_uamp_use_check_box, + SIGNAL(stateChanged(int)), this, + SLOT(handleUampMonitorsOnOff(int))); +} + +void SANSBackgroundCorrectionWidget::handleTimeDetectorsOnOff(int stateInt) { + auto state = convertQtInt(stateInt); + m_ui.bckgnd_cor_det_time_run_line_edit->setEnabled(state); + m_ui.bckgnd_cor_det_mean_check_box->setEnabled(state); +} + +void SANSBackgroundCorrectionWidget::handleUampDetectorsOnOff(int stateInt) { + auto state = convertQtInt(stateInt); + m_ui.bckgnd_cor_det_uamp_run_line_edit->setEnabled(state); +} + +void SANSBackgroundCorrectionWidget::handleTimeMonitorsOnOff(int stateInt) { + auto state = convertQtInt(stateInt); + m_ui.bckgnd_cor_mon_time_run_line_edit->setEnabled(state); + m_ui.bckgnd_cor_mon_mean_check_box->setEnabled(state); + m_ui.bckgnd_cor_mon_time_mon_num_line_edit->setEnabled(state); +} + +void SANSBackgroundCorrectionWidget::handleUampMonitorsOnOff(int stateInt) { + auto state = convertQtInt(stateInt); + m_ui.bckgnd_cor_mon_uamp_run_line_edit->setEnabled(state); + m_ui.bckgnd_cor_mon_uamp_mon_num_line_edit->setEnabled(state); +} + +void SANSBackgroundCorrectionWidget::resetEntries() { + m_ui.bckgnd_cor_det_time_use_check_box->setChecked(false); + m_ui.bckgnd_cor_det_uamp_use_check_box->setChecked(false); + m_ui.bckgnd_cor_mon_time_use_check_box->setChecked(false); + m_ui.bckgnd_cor_mon_uamp_use_check_box->setChecked(false); + + m_ui.bckgnd_cor_det_time_run_line_edit->setText(""); + m_ui.bckgnd_cor_det_uamp_run_line_edit->setText(""); + m_ui.bckgnd_cor_mon_time_run_line_edit->setText(""); + m_ui.bckgnd_cor_mon_time_mon_num_line_edit->setText(""); + m_ui.bckgnd_cor_mon_uamp_run_line_edit->setText(""); + m_ui.bckgnd_cor_mon_uamp_mon_num_line_edit->setText(""); +} +} +} \ No newline at end of file diff --git a/MantidQt/CustomInterfaces/src/SANSRunWindow.cpp b/MantidQt/CustomInterfaces/src/SANSRunWindow.cpp index e61ab45ee962e7b2c586f4e296a2b9be6415034b..004700917eb96d514beb9685f25c0e834d2e2774 100644 --- a/MantidQt/CustomInterfaces/src/SANSRunWindow.cpp +++ b/MantidQt/CustomInterfaces/src/SANSRunWindow.cpp @@ -1,42 +1,43 @@ //---------------------- // Includes //---------------------- -#include "MantidQtCustomInterfaces/SANSRunWindow.h" -#include "MantidQtCustomInterfaces/SANSAddFiles.h" -#include "MantidQtAPI/ManageUserDirectories.h" -#include "MantidQtAPI/FileDialogHandler.h" #include "MantidKernel/ConfigService.h" #include "MantidKernel/FacilityInfo.h" #include "MantidKernel/PropertyWithValue.h" +#include "MantidQtAPI/FileDialogHandler.h" +#include "MantidQtAPI/ManageUserDirectories.h" +#include "MantidQtCustomInterfaces/SANSAddFiles.h" +#include "MantidQtCustomInterfaces/SANSBackgroundCorrectionSettings.h" +#include "MantidQtCustomInterfaces/SANSRunWindow.h" -#include "MantidKernel/Logger.h" -#include "MantidKernel/Exception.h" -#include "MantidAPI/FrameworkManager.h" -#include "MantidAPI/IAlgorithm.h" #include "MantidAPI/AlgorithmManager.h" #include "MantidAPI/AnalysisDataService.h" +#include "MantidAPI/FrameworkManager.h" +#include "MantidAPI/IAlgorithm.h" +#include "MantidAPI/IEventWorkspace.h" #include "MantidAPI/PropertyManagerDataService.h" +#include "MantidAPI/Run.h" #include "MantidAPI/WorkspaceGroup.h" -#include "MantidAPI/IEventWorkspace.h" -#include "MantidGeometry/Instrument.h" #include "MantidGeometry/IComponent.h" -#include "MantidKernel/V3D.h" +#include "MantidGeometry/Instrument.h" #include "MantidKernel/Exception.h" -#include "MantidAPI/Run.h" +#include "MantidKernel/Exception.h" +#include "MantidKernel/Logger.h" +#include "MantidKernel/V3D.h" -#include <QLineEdit> -#include <QHash> -#include <QTextStream> -#include <QTreeWidgetItem> -#include <QMessageBox> -#include <QInputDialog> -#include <QSignalMapper> -#include <QHeaderView> #include <QApplication> #include <QClipboard> -#include <QTemporaryFile> #include <QDateTime> #include <QDesktopServices> +#include <QHash> +#include <QHeaderView> +#include <QInputDialog> +#include <QLineEdit> +#include <QMessageBox> +#include <QSignalMapper> +#include <QTemporaryFile> +#include <QTextStream> +#include <QTreeWidgetItem> #include <QUrl> #include <Poco/StringTokenizer.h> @@ -148,6 +149,36 @@ void setStringSetting(const QString &settingName, const QString &settingValue) { else settings->setProperty(name, value); } + +/** + * Converts a c++ bool into a Python string representation. + * @param input: a c++ bool + * @returns a string which is either True or False + */ +QString convertBoolToPythonBoolString(bool input) { + return input + ? MantidQt::CustomInterfaces::SANSConstants::getPythonTrueKeyword() + : MantidQt::CustomInterfaces::SANSConstants:: + getPythonFalseKeyword(); +} + +/** + * Converts string representation of a Python bool to a C++ bool + * @param input: the python string representation + * @returns a true or false +*/ +bool convertPythonBoolStringToBool(QString input) { + bool value = false; + if (input == + MantidQt::CustomInterfaces::SANSConstants::getPythonTrueKeyword()) { + value = true; + } else if (input == MantidQt::CustomInterfaces::SANSConstants:: + getPythonFalseKeyword()) { + value = false; + } + + return value; +} } //---------------------------------------------- @@ -794,7 +825,8 @@ bool SANSRunWindow::loadUserFile() { QString errors = runReduceScriptFunction("print " "i.ReductionSingleton().user_settings.execute(i." - "ReductionSingleton())").trimmed(); + "ReductionSingleton())") + .trimmed(); // create a string list with a string for each line const QStringList allOutput = errors.split("\n"); errors.clear(); @@ -820,8 +852,9 @@ bool SANSRunWindow::loadUserFile() { runReduceScriptFunction("print i.ReductionSingleton().mask.min_radius") .toDouble(); m_uiForm.rad_min->setText(QString::number(dbl_param * unit_conv)); - dbl_param = runReduceScriptFunction( - "print i.ReductionSingleton().mask.max_radius").toDouble(); + dbl_param = + runReduceScriptFunction("print i.ReductionSingleton().mask.max_radius") + .toDouble(); m_uiForm.rad_max->setText(QString::number(dbl_param * unit_conv)); // EventsTime m_uiForm.l_events_binning->setText( @@ -831,10 +864,12 @@ bool SANSRunWindow::loadUserFile() { "print i.ReductionSingleton().to_wavelen.wav_low")); m_uiForm.wav_max->setText( runReduceScriptFunction( - "print i.ReductionSingleton().to_wavelen.wav_high").trimmed()); + "print i.ReductionSingleton().to_wavelen.wav_high") + .trimmed()); const QString wav_step = runReduceScriptFunction( - "print i.ReductionSingleton().to_wavelen.wav_step").trimmed(); + "print i.ReductionSingleton().to_wavelen.wav_step") + .trimmed(); setLimitStepParameter("wavelength", wav_step, m_uiForm.wav_dw, m_uiForm.wav_dw_opt); // Q @@ -866,20 +901,24 @@ bool SANSRunWindow::loadUserFile() { m_uiForm.frontDetRescale->setText( runReduceScriptFunction("print " "i.ReductionSingleton().instrument.getDetector('" - "FRONT').rescaleAndShift.scale").trimmed()); + "FRONT').rescaleAndShift.scale") + .trimmed()); m_uiForm.frontDetShift->setText( runReduceScriptFunction("print " "i.ReductionSingleton().instrument.getDetector('" - "FRONT').rescaleAndShift.shift").trimmed()); + "FRONT').rescaleAndShift.shift") + .trimmed()); QString fitScale = runReduceScriptFunction("print " "i.ReductionSingleton().instrument.getDetector('" - "FRONT').rescaleAndShift.fitScale").trimmed(); + "FRONT').rescaleAndShift.fitScale") + .trimmed(); QString fitShift = runReduceScriptFunction("print " "i.ReductionSingleton().instrument.getDetector('" - "FRONT').rescaleAndShift.fitShift").trimmed(); + "FRONT').rescaleAndShift.fitShift") + .trimmed(); if (fitScale == "True") m_uiForm.frontDetRescaleCB->setChecked(true); @@ -901,11 +940,13 @@ bool SANSRunWindow::loadUserFile() { m_uiForm.frontDetQmin->setText( runReduceScriptFunction("print " "i.ReductionSingleton().instrument.getDetector(" - "'FRONT').rescaleAndShift.qMin").trimmed()); + "'FRONT').rescaleAndShift.qMin") + .trimmed()); m_uiForm.frontDetQmax->setText( runReduceScriptFunction("print " "i.ReductionSingleton().instrument.getDetector(" - "'FRONT').rescaleAndShift.qMax").trimmed()); + "'FRONT').rescaleAndShift.qMax") + .trimmed()); } else m_uiForm.frontDetQrangeOnOff->setChecked(false); @@ -955,15 +996,15 @@ bool SANSRunWindow::loadUserFile() { m_uiForm.enableFrontFlood_ck->checkState() == Qt::Checked); // Scale factor - dbl_param = - runReduceScriptFunction( - "print i.ReductionSingleton()._corr_and_scale.rescale").toDouble(); + dbl_param = runReduceScriptFunction( + "print i.ReductionSingleton()._corr_and_scale.rescale") + .toDouble(); m_uiForm.scale_factor->setText(QString::number(dbl_param / 100.)); // Sample offset if one has been specified - dbl_param = - runReduceScriptFunction( - "print i.ReductionSingleton().instrument.SAMPLE_Z_CORR").toDouble(); + dbl_param = runReduceScriptFunction( + "print i.ReductionSingleton().instrument.SAMPLE_Z_CORR") + .toDouble(); m_uiForm.smpl_offset->setText(QString::number(dbl_param * unit_conv)); // Centre coordinates @@ -972,9 +1013,9 @@ bool SANSRunWindow::loadUserFile() { // Set the beam finder specific settings setBeamFinderDetails(); // get the scale factor1 for the beam centre to scale it correctly - dbl_param = - runReduceScriptFunction( - "print i.ReductionSingleton().get_beam_center('rear')[0]").toDouble(); + dbl_param = runReduceScriptFunction( + "print i.ReductionSingleton().get_beam_center('rear')[0]") + .toDouble(); double dbl_paramsf = runReduceScriptFunction( "print i.ReductionSingleton().get_beam_center_scale_factor1()") @@ -985,9 +1026,9 @@ bool SANSRunWindow::loadUserFile() { runReduceScriptFunction( "print i.ReductionSingleton().get_beam_center_scale_factor2()") .toDouble(); - dbl_param = - runReduceScriptFunction( - "print i.ReductionSingleton().get_beam_center('rear')[1]").toDouble(); + dbl_param = runReduceScriptFunction( + "print i.ReductionSingleton().get_beam_center('rear')[1]") + .toDouble(); m_uiForm.rear_beam_y->setText(QString::number(dbl_param * dbl_paramsf)); // front dbl_param = runReduceScriptFunction( @@ -1011,14 +1052,15 @@ bool SANSRunWindow::loadUserFile() { // Read the extra length for the gravity correction const double extraLengthParam = runReduceScriptFunction( - "print i.ReductionSingleton().to_Q.get_extra_length()").toDouble(); + "print i.ReductionSingleton().to_Q.get_extra_length()") + .toDouble(); m_uiForm.gravity_extra_length_line_edit->setText( QString::number(extraLengthParam)); ////Detector bank: support REAR, FRONT, HAB, BOTH, MERGED, MERGE options - QString detName = - runReduceScriptFunction( - "print i.ReductionSingleton().instrument.det_selection").trimmed(); + QString detName = runReduceScriptFunction( + "print i.ReductionSingleton().instrument.det_selection") + .trimmed(); if (detName == "REAR" || detName == "MAIN") { m_uiForm.detbank_sel->setCurrentIndex(0); @@ -1042,6 +1084,10 @@ bool SANSRunWindow::loadUserFile() { // Setup the QResolution retrieveQResolutionSettings(); + // Setup the BackgroundCorrection + initializeBackgroundCorrection(); + retrieveBackgroundCorrection(); + if (runReduceScriptFunction("print i.ReductionSingleton().mask.phi_mirror") .trimmed() == "True") { m_uiForm.mirror_phi->setChecked(true); @@ -1915,7 +1961,8 @@ void SANSRunWindow::saveFileBrowse() { QString prevPath = prevValues.value("dir", QString::fromStdString( ConfigService::Instance().getString( - "defaultsave.directory"))).toString(); + "defaultsave.directory"))) + .toString(); const QString filter = ";;AllFiles (*.*)"; @@ -2307,6 +2354,9 @@ QString SANSRunWindow::readUserFileGUIChanges(const States type) { // Set the QResolution settings writeQResolutionSettingsToPythonScript(exec_reduce); + // Set the BackgroundCorrection settings + writeBackgroundCorrectionToPythonScript(exec_reduce); + // set the user defined center (Geometry Tab) // this information is used just after loading the data in order to move to // the center @@ -2834,7 +2884,8 @@ void SANSRunWindow::handleRunFindCentre() { QString errors = runReduceScriptFunction("print " "i.ReductionSingleton().user_settings.execute(i." - "ReductionSingleton())").trimmed(); + "ReductionSingleton())") + .trimmed(); g_centreFinderLog.notice() << result.toStdString() << "\n"; @@ -3060,7 +3111,8 @@ void SANSRunWindow::handleInstrumentChange() { "print i.ReductionSingleton().instrument.cur_detector().name()"); QString detectorSelection = runReduceScriptFunction( - "print i.ReductionSingleton().instrument.det_selection").trimmed(); + "print i.ReductionSingleton().instrument.det_selection") + .trimmed(); int ind = m_uiForm.detbank_sel->findText(detect); // We set the detector selection only if nothing is set yet. // Previously, we didn't handle merged and both at this point @@ -3283,14 +3335,14 @@ void SANSRunWindow::updateTransInfo(int state) { if (state == Qt::Checked) { _min->setEnabled(true); - _min->setText( - runReduceScriptFunction( - "print i.ReductionSingleton().instrument.WAV_RANGE_MIN").trimmed()); + _min->setText(runReduceScriptFunction( + "print i.ReductionSingleton().instrument.WAV_RANGE_MIN") + .trimmed()); _max->setEnabled(true); - _max->setText( - runReduceScriptFunction( - "print i.ReductionSingleton().instrument.WAV_RANGE_MAX").trimmed()); + _max->setText(runReduceScriptFunction( + "print i.ReductionSingleton().instrument.WAV_RANGE_MAX") + .trimmed()); } else { _min->setEnabled(false); @@ -3782,7 +3834,8 @@ void SANSRunWindow::loadTransmissionSettings() { QString transMin = runReduceScriptFunction("print " "i.ReductionSingleton().transmission_calculator." - "lambdaMin('SAMPLE')").trimmed(); + "lambdaMin('SAMPLE')") + .trimmed(); if (transMin == "None") { m_uiForm.transFit_ck->setChecked(false); } else { @@ -3791,13 +3844,15 @@ void SANSRunWindow::loadTransmissionSettings() { m_uiForm.trans_max->setText( runReduceScriptFunction("print " "i.ReductionSingleton().transmission_" - "calculator.lambdaMax('SAMPLE')").trimmed()); + "calculator.lambdaMax('SAMPLE')") + .trimmed()); } QString text = runReduceScriptFunction("print " "i.ReductionSingleton().transmission_calculator." - "fitMethod('SAMPLE')").trimmed(); + "fitMethod('SAMPLE')") + .trimmed(); int index = m_uiForm.trans_opt->findText(text, Qt::MatchFixedString); if (index >= 0) { m_uiForm.trans_opt->setCurrentIndex(index); @@ -3809,7 +3864,8 @@ void SANSRunWindow::loadTransmissionSettings() { transMin = runReduceScriptFunction("print " "i.ReductionSingleton().transmission_" - "calculator.lambdaMin('CAN')").trimmed(); + "calculator.lambdaMin('CAN')") + .trimmed(); if (transMin == "None") { m_uiForm.transFit_ck_can->setChecked(false); } else { @@ -3818,11 +3874,13 @@ void SANSRunWindow::loadTransmissionSettings() { m_uiForm.trans_max_can->setText( runReduceScriptFunction("print " "i.ReductionSingleton().transmission_" - "calculator.lambdaMax('CAN')").trimmed()); + "calculator.lambdaMax('CAN')") + .trimmed()); } text = runReduceScriptFunction("print " "i.ReductionSingleton().transmission_" - "calculator.fitMethod('CAN')").trimmed(); + "calculator.fitMethod('CAN')") + .trimmed(); index = m_uiForm.trans_opt_can->findText(text, Qt::MatchFixedString); if (index >= 0) { m_uiForm.trans_opt_can->setCurrentIndex(index); @@ -4489,7 +4547,8 @@ void SANSRunWindow::updateBeamCenterCoordinates() { // from the ticket #5942 both detectors have center coordinates double dbl_param = runReduceScriptFunction( - "print i.ReductionSingleton().get_beam_center('rear')[0]").toDouble(); + "print i.ReductionSingleton().get_beam_center('rear')[0]") + .toDouble(); // get the scale factor1 for the beam centre to scale it correctly double dbl_paramsf = runReduceScriptFunction( @@ -4501,9 +4560,9 @@ void SANSRunWindow::updateBeamCenterCoordinates() { runReduceScriptFunction( "print i.ReductionSingleton().get_beam_center_scale_factor2()") .toDouble(); - dbl_param = - runReduceScriptFunction( - "print i.ReductionSingleton().get_beam_center('rear')[1]").toDouble(); + dbl_param = runReduceScriptFunction( + "print i.ReductionSingleton().get_beam_center('rear')[1]") + .toDouble(); m_uiForm.rear_beam_y->setText(QString::number(dbl_param * dbl_paramsf)); // front dbl_param = runReduceScriptFunction( @@ -4524,9 +4583,9 @@ void SANSRunWindow::setBeamFinderDetails() { auto instrumentName = m_uiForm.inst_opt->currentText(); // Set the labels according to the instrument - auto requiresAngle = - runReduceScriptFunction( - "print i.is_current_workspace_an_angle_workspace()").simplified(); + auto requiresAngle = runReduceScriptFunction( + "print i.is_current_workspace_an_angle_workspace()") + .simplified(); QString labelPosition; if (requiresAngle == m_constants.getPythonTrueKeyword()) { labelPosition = "Current ( " + QString(QChar(0x03B2)) + " , y ) ["; @@ -4860,6 +4919,118 @@ void SANSRunWindow::initQResolutionSettings() { m_constants.getQResolutionA2ToolTipText()); } +/** + * Initialize the background corrections, ie reset all fields + */ +void SANSRunWindow::initializeBackgroundCorrection() { + m_uiForm.sansBackgroundCorrectionWidget->resetEntries(); +} + +/** + * Retrieve background correction settings and set them in the UI + */ +void SANSRunWindow::retrieveBackgroundCorrection() { + // Get all settings from the python side + auto timeDetector = retrieveBackgroundCorrectionSetting(true, false); + auto timeMonitor = retrieveBackgroundCorrectionSetting(true, true); + auto uampDetector = retrieveBackgroundCorrectionSetting(false, false); + auto uampMonitor = retrieveBackgroundCorrectionSetting(false, true); + + // Apply the settings to the background correction widget + m_uiForm.sansBackgroundCorrectionWidget->setDarkRunSettingForTimeDetectors( + timeDetector); + m_uiForm.sansBackgroundCorrectionWidget->setDarkRunSettingForTimeMonitors( + timeMonitor); + m_uiForm.sansBackgroundCorrectionWidget->setDarkRunSettingForUampDetectors( + uampDetector); + m_uiForm.sansBackgroundCorrectionWidget->setDarkRunSettingForUampMonitors( + uampMonitor); +} + +/** + * Get a single background correction setting + * @param isTime: if is time or uamp + * @param isMon: if is monitor or detector + * @returns a settings object + */ +SANSBackgroundCorrectionSettings +SANSRunWindow::retrieveBackgroundCorrectionSetting(bool isTime, bool isMon) { + std::map<QString, QString> commandMap = { + {"run_number", ""}, {"is_mean", ""}, {"is_mon", ""}, {"mon_number", ""}}; + + auto createPythonScript = [](bool isTime, bool isMon, QString component) { + return "i.get_background_correction(is_time = " + + convertBoolToPythonBoolString(isTime) + ", is_mon=" + + convertBoolToPythonBoolString(isMon) + ", component='" + component + + "')"; + }; + + for (auto &command : commandMap) { + auto element = + runPythonCode(createPythonScript(isTime, isMon, command.first)); + element = element.simplified(); + if (element != m_constants.getPythonEmptyKeyword()) { + command.second = element; + } + } + + QString runNumber = commandMap["run_number"]; + bool useMean = convertPythonBoolStringToBool(commandMap["is_mean"]); + bool useMon = convertPythonBoolStringToBool(commandMap["is_mon"]); + QString monNumber = commandMap["mon_number"]; + + return SANSBackgroundCorrectionSettings(runNumber, useMean, useMon, + monNumber); +} + +/** + * Sends the background correction user setting + * @param pythonCode: the python code to attaceh the new commands + */ +void SANSRunWindow::writeBackgroundCorrectionToPythonScript( + QString &pythonCode) { + // Clear the stored settings. Else we will overwrite settings + runPythonCode("i.clear_background_correction()"); + + // Get the settings + auto timeDetectors = m_uiForm.sansBackgroundCorrectionWidget + ->getDarkRunSettingForTimeDetectors(); + auto timeMonitors = m_uiForm.sansBackgroundCorrectionWidget + ->getDarkRunSettingForTimeMonitors(); + + auto uampDetectors = m_uiForm.sansBackgroundCorrectionWidget + ->getDarkRunSettingForUampDetectors(); + auto uampMonitors = m_uiForm.sansBackgroundCorrectionWidget + ->getDarkRunSettingForUampMonitors(); + + addBackgroundCorrectionToPythonScript(pythonCode, timeDetectors, true); + addBackgroundCorrectionToPythonScript(pythonCode, timeMonitors, true); + + addBackgroundCorrectionToPythonScript(pythonCode, uampDetectors, false); + addBackgroundCorrectionToPythonScript(pythonCode, uampMonitors, false); +} + +/** + * Add specific background correction setting to python script + * @param pythonCode: the python code to attaceh the new commands + * @param setting: a background correction settings object + * @param isTimeBased: flag if it is time-based + */ +void SANSRunWindow::addBackgroundCorrectionToPythonScript( + QString &pythonCode, + MantidQt::CustomInterfaces::SANSBackgroundCorrectionSettings setting, + bool isTimeBased) { + + QString newSetting = + "i.set_background_correction(run_number='" + setting.getRunNumber() + + "'," + "is_time_based=" + convertBoolToPythonBoolString(isTimeBased) + + "," + "is_mon=" + convertBoolToPythonBoolString(setting.getUseMon()) + + "," + "is_mean=" + convertBoolToPythonBoolString(setting.getUseMean()) + + "," + "mon_numbers = '" + setting.getMonNumber() + "')\n"; + + pythonCode += newSetting; +} + /** * Check if the user file has a valid extension */ diff --git a/Testing/Data/SystemTest/SANS2D/SANS2D00028797_removed_spectra.nxs.md5 b/Testing/Data/SystemTest/SANS2D/SANS2D00028797_removed_spectra.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..7adeee3fd86d81bdeaedd8c24a0be5c6f3153c0f --- /dev/null +++ b/Testing/Data/SystemTest/SANS2D/SANS2D00028797_removed_spectra.nxs.md5 @@ -0,0 +1 @@ +9067fc19fa4f9ee879fe03fdb0ff5ac7 diff --git a/Testing/Data/SystemTest/SANS2D/SANS2D00028827_removed_spectra.nxs.md5 b/Testing/Data/SystemTest/SANS2D/SANS2D00028827_removed_spectra.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..a1a9d88dcc2a663deca6785852461c90d9fc36f4 --- /dev/null +++ b/Testing/Data/SystemTest/SANS2D/SANS2D00028827_removed_spectra.nxs.md5 @@ -0,0 +1 @@ +bae0b76b6dad63028c9331358afc75eb diff --git a/Testing/SystemTests/tests/analysis/SANS2DMultiPeriodAddFiles.py b/Testing/SystemTests/tests/analysis/SANS2DMultiPeriodAddFiles.py index 0b669615c70a0844b644fe236b6e13e3bf54a571..e8fe0a5b612dd6f216be591539eedec86812faf5 100644 --- a/Testing/SystemTests/tests/analysis/SANS2DMultiPeriodAddFiles.py +++ b/Testing/SystemTests/tests/analysis/SANS2DMultiPeriodAddFiles.py @@ -1,4 +1,4 @@ -#pylint: disable=no-init +#pylint: disable=no-init import stresstesting from mantid.simpleapi import * from mantid import config diff --git a/Testing/SystemTests/tests/analysis/SANSDarkRunSubtractionTest.py b/Testing/SystemTests/tests/analysis/SANSDarkRunSubtractionTest.py new file mode 100644 index 0000000000000000000000000000000000000000..aa21a059b93130c9967c89202ef80736b8c1d674 --- /dev/null +++ b/Testing/SystemTests/tests/analysis/SANSDarkRunSubtractionTest.py @@ -0,0 +1,661 @@ +#pylint: disable=no-init +#pylint: disable=invalid-name +#pylint: disable=too-many-arguments +#pylint: disable=too-many-public-methods +import unittest +import stresstesting +from mantid.simpleapi import * +from isis_reduction_steps import DarkRunSubtraction +from SANSUserFileParser import DarkRunSettings +from SANSUtility import getFileAndName + + +class DarkRunSubtractionTest(unittest.TestCase): + def test_that_specifying_more_than_two_run_numbers_per_category_raises_error(self): + # Arrange + dark_run_subtractor = DarkRunSubtraction() + # Time-based detectors + setting1 = self._get_dark_run_settings_object("111111", True, False, False, None) + setting2 = self._get_dark_run_settings_object("222222", True, False, False, None) + # Uamp-based detectors + setting3 = self._get_dark_run_settings_object("111111", False, False, False, None) + setting4 = self._get_dark_run_settings_object("222222", False, False, False, None) + # Time-based monitors + setting5 = self._get_dark_run_settings_object("111111", True, False, True, None) + setting6 = self._get_dark_run_settings_object("222222", True, False, True, None) + # Uamp-based monitors + setting7 = self._get_dark_run_settings_object("111111", False, False, True, None) + setting8 = self._get_dark_run_settings_object("222222", False, False, True, None) + + dark_run_subtractor.add_setting(setting1) + dark_run_subtractor.add_setting(setting2) + dark_run_subtractor.add_setting(setting3) + dark_run_subtractor.add_setting(setting4) + dark_run_subtractor.add_setting(setting5) + dark_run_subtractor.add_setting(setting6) + dark_run_subtractor.add_setting(setting7) + dark_run_subtractor.add_setting(setting8) + # Act + Assert + self.assertRaises(RuntimeError, dark_run_subtractor.get_time_based_setting_detectors) + self.assertRaises(RuntimeError, dark_run_subtractor.get_uamp_based_setting_detectors) + self.assertRaises(RuntimeError, dark_run_subtractor.get_time_based_setting_monitors) + self.assertRaises(RuntimeError, dark_run_subtractor.get_uamp_based_setting_monitors) + + def test_that_raises_when_detector_has_more_than_one_setting(self): + # Arrange + dark_run_subtractor = DarkRunSubtraction() + # We have two settings with different run numbers for detecetor-type corrections + setting1 = self._get_dark_run_settings_object("222222", True, False, False, None) + setting2 = self._get_dark_run_settings_object("222222", True, False, False, None) + dark_run_subtractor.add_setting(setting1) + dark_run_subtractor.add_setting(setting2) + # Act + Assert + self.assertRaises(RuntimeError, dark_run_subtractor.get_time_based_setting_detectors) + + def test_that_raises_when_having_mixed_mean_settings_for_monitor_time(self): + # Arrange + dark_run_subtractor = DarkRunSubtraction() + # When having two monitor settings with differing mean selections, this is inconsistent + setting1 = self._get_dark_run_settings_object("222222", True, True, True, None) + setting2 = self._get_dark_run_settings_object("222222", True, False, True, None) + dark_run_subtractor.add_setting(setting1) + dark_run_subtractor.add_setting(setting2) + # Act + Assert + self.assertRaises(RuntimeError, dark_run_subtractor.get_time_based_setting_detectors) + + def test_that_subtracts_with_correct_single_dark_run_for_detector(self): + # Arrange + use_time = False + use_mean = False + use_mon = False + mon_number = None + run_number = self._get_dark_file() + + is_input_event = True + + settings = [] + setting = self._get_dark_run_settings_object(run_number, use_time, use_mean, use_mon, mon_number) + settings.append(setting) + + # Act + Assert + scatter_workspace, monitor_workspace = self._do_test_valid(settings, is_input_event) + + expected_num_spectr_ws = 20 - 9 + 1 + self.assertTrue(scatter_workspace.getNumberHistograms() == expected_num_spectr_ws, "Should have 10 spectra") + + # Since in this test we use the same file for the scatterer and the dark run, we expect + # that the detectors are 0. This is because we subtract bin by bin when using UAMP + all_entries_zero = lambda ws, index : all([0.0 == element for element in ws.dataY(index)]) + + for i in range(0, scatter_workspace.getNumberHistograms()): + self.assertTrue(all_entries_zero(scatter_workspace, i), "Detector entries should all be 0") + + # The monitors should not be affected, but we only have data in ws_index 0-3 + for i in [0,3]: + self.assertFalse(all_entries_zero(monitor_workspace, i), "Monitor entries should not all be 0") + + def test_that_subtracts_with_correct_single_dark_run_and_multiple_settings(self): + # Arrange + use_time_1 = False + use_mean_1 = False + use_mon_1 = False + mon_number_1 = None + run_number = self._get_dark_file() + + use_time_2 = False + use_mean_2 = False + use_mon_2 = True + mon_number_2 = [2] # We are selecting detector ID 2 this corresponds to workspace index 1 + ws_index2 = [1] + + settings = [] + setting1 = self._get_dark_run_settings_object(run_number, use_time_1, use_mean_1, + use_mon_1, mon_number_1) + setting2 = self._get_dark_run_settings_object(run_number, use_time_2, use_mean_2, + use_mon_2, mon_number_2) + settings.append(setting1) + settings.append(setting2) + + # Act + Assert + scatter_workspace, monitor_workspace = self._do_test_valid(settings) + + expected_num_spectr_ws = 20 - 9 + 1 # Total number of spectra in the original workspace from 9 to 245798 + self.assertTrue(scatter_workspace.getNumberHistograms() == expected_num_spectr_ws, "Should have 8 spectra") + + # Expect all entries to be 0 except for monitor 0. We selected monitor 1 and all detectors + # for the subtraction. Hence only moniotr 0 would have been spared. + all_entries_zero = lambda ws, index : all([0.0 == element for element in ws.dataY(index)]) + + for i in range(0, scatter_workspace.getNumberHistograms()): + self.assertTrue(all_entries_zero(scatter_workspace, i), "Detector entries should all be 0") + + for i in [0,2,3]: + self.assertFalse(all_entries_zero(monitor_workspace, i), "Monitor entries should not all be 0") + + for i in ws_index2: + self.assertTrue(all_entries_zero(monitor_workspace, i), "Entries should all be 0") + + def test_that_subtracts_correct_added_file_type(self): + # Arrange + use_time = False + use_mean = False + use_mon = False + mon_number = None + + # Create added workspace and have it saved out + import SANSadd2 + SANSadd2.add_runs(('SANS2D00028827_removed_spectra.nxs','SANS2D00028797_removed_spectra.nxs'),'SANS2DTUBES', '.nxs', + rawTypes=('.add','.raw','.s*'), lowMem=False, + saveAsEvent=True, isOverlay = False) + run_number = r'SANS2D00028797_removed_spectra-add.nxs' + settings = [] + setting = self._get_dark_run_settings_object(run_number, use_time, use_mean, use_mon, mon_number) + settings.append(setting) + + # Act + Assert + is_event_ws = True + scatter_workspace, monitor_workspace = self._do_test_valid(settings, is_event_ws, run_number) + expected_num_spectr_ws = 20 - 9 + 1 + self.assertTrue(scatter_workspace.getNumberHistograms() == expected_num_spectr_ws, "Should have 8 spectra") + + # Since in this test we use the same file for the scatterer and the dark run, we expect + # that the detectors are 0. This is because we subtract bin by bin when using UAMP + all_entries_zero = lambda ws, index : all([0.0 == element for element in ws.dataY(index)]) + + for i in range(0, scatter_workspace.getNumberHistograms()): + self.assertTrue(all_entries_zero(scatter_workspace, i), "Detector entries should all be 0") + + # The monitors should not be affected, but we only have data in ws_index 0-3 + for i in [0,3]: + self.assertFalse(all_entries_zero(monitor_workspace, i), "Monitor entries should not all be 0") + + os.remove(os.path.join(config['defaultsave.directory'],run_number)) + + def test_that_subtracts_correct_added_file_type_when_only_monitor_subtracted(self): + # Arrange + use_time = False + use_mean = False + use_mon = True + mon_number = [2] # We are selecting detector ID 2 this corresponds to workspace index 1 + ws_index = [1] + + # Create added workspace and have it saved out + import SANSadd2 + SANSadd2.add_runs(('SANS2D00028827_removed_spectra.nxs','SANS2D00028797_removed_spectra.nxs'),'SANS2DTUBES', '.nxs', + rawTypes=('.add','.raw','.s*'), lowMem=False, + saveAsEvent=True, isOverlay = False) + run_number = r'SANS2D00028797_removed_spectra-add.nxs' + settings = [] + setting = self._get_dark_run_settings_object(run_number, use_time, use_mean, use_mon, mon_number) + settings.append(setting) + + # Act + Assert + is_event_ws = True + scatter_workspace, monitor_workspace = self._do_test_valid(settings, is_event_ws, run_number) + + expected_num_spectr_ws = 20 - 9 + 1 # Total number of spectra in the original workspace from 9 to 245798 + self.assertTrue(scatter_workspace.getNumberHistograms() == expected_num_spectr_ws, "Should have 8 spectra") + + # Since in this test we use the same file for the scatterer and the dark run, we expect + # that the detectors are 0. This is because we subtract bin by bin when using UAMP + all_entries_zero = lambda ws, index : all([0.0 == element for element in ws.dataY(index)]) + + # Some spectra might be zero, so we have to check that there is something which is not zero + all_detectors_zero = True + for i in range(0, scatter_workspace.getNumberHistograms()): + all_detectors_zero = all_detectors_zero & all_entries_zero(scatter_workspace, i) + self.assertFalse(all_detectors_zero, "There should be some detectors which are not zero") + + # The monitors should not be affected, but we only have data in ws_index 0-3 + for i in [0,2,3]: + self.assertFalse(all_entries_zero(monitor_workspace, i), "Monitor1, Monitor3, Monitor4 entries should not all be 0") + + # Monitor 2 (workspace index 1 should be 0 + for i in ws_index: + self.assertTrue(all_entries_zero(monitor_workspace, i), "Monitor2 entries should all be 0") + + os.remove(os.path.join(config['defaultsave.directory'],run_number)) + + def test_that_subtracts_correct_for_histo_input_workspace(self): + # Arrange + use_time = False + use_mean = False + use_mon = True + mon_number = [2] + ws_index = [1] + + run_number = self._get_dark_file() + settings = [] + setting = self._get_dark_run_settings_object(run_number, use_time, use_mean, use_mon, mon_number) + settings.append(setting) + + is_event_input = False + # Act + Assert + scatter_workspace, monitor_workspace = self._do_test_valid(settings, is_event_input, run_number) + + expected_num_spectr_ws = 20 - 9 + 1 + self.assertTrue(scatter_workspace.getNumberHistograms() == expected_num_spectr_ws, "Should have 8 spectra") + + # Since in this test we use the same file for the scatterer and the dark run, we expect + # that the detectors are 0. This is because we subtract bin by bin when using UAMP + all_entries_zero = lambda ws, index : all([0.0 == element for element in ws.dataY(index)]) + + # Some spectra might be zero, so we have to check that there is something which is not zero + all_detectors_zero = True + for i in range(0, scatter_workspace.getNumberHistograms()): + all_detectors_zero = all_detectors_zero & all_entries_zero(scatter_workspace, i) + self.assertFalse(all_detectors_zero, "There should be some detectors which are not zero") + + # The monitors should not be affected, but we only have data in ws_index 0-3 + for i in [0,2,3]: + self.assertFalse(all_entries_zero(monitor_workspace, i), "Monitor1, Monitor3, Monitor4 entries should not all be 0") + + # Monitor 1 should be 0 + for i in ws_index: + self.assertTrue(all_entries_zero(monitor_workspace, i), "Monitor2 entries should all be 0") + + def test_that_subtracts_correct_for_transmission_workspace_with_only_monitors(self): + # Arrange + use_time = False + use_mean = False + use_mon = True + mon_number = [2] + ws_index = [1] + + run_number = self._get_dark_file() + settings = [] + setting = self._get_dark_run_settings_object(run_number, use_time, use_mean, use_mon, mon_number) + settings.append(setting) + + trans_ids = [1,2,3,4] + + # Act + Assert + transmission_workspace = self._do_test_valid_transmission(settings, trans_ids) + + expected_num_spectr_ws = len(trans_ids) + self.assertTrue(transmission_workspace.getNumberHistograms() == expected_num_spectr_ws, "Should have 4 spectra") + + # Since in this test we use the same file for the scatterer and the dark run, we expect + # that the detectors are 0. This is because we subtract bin by bin when using UAMP + all_entries_zero = lambda ws, index : all([0.0 == element for element in ws.dataY(index)]) + + # We only have monitors in our transmission file, monitor 2 should be 0 + for i in [0,2,3]: + self.assertFalse(all_entries_zero(transmission_workspace, i), "Monitor1, Monitor3, Monitor4 entries should not all be 0") + + # Monitor2 should be 0 + for i in ws_index: + self.assertTrue(all_entries_zero(transmission_workspace, i), "Monitor2 entries should all be 0") + + def test_that_subtracts_nothing_when_selecting_detector_subtraction_for_transmission_workspace_with_only_monitors(self): + # Arrange + use_time = False + use_mean = False + use_mon = False + mon_number = None + + run_number = self._get_dark_file() + settings = [] + setting = self._get_dark_run_settings_object(run_number, use_time, use_mean, use_mon, mon_number) + settings.append(setting) + + trans_ids = [1,2,3,4] + + # Act + Assert + transmission_workspace = self._do_test_valid_transmission(settings, trans_ids) + + expected_num_spectr_ws = len(trans_ids) + self.assertTrue(transmission_workspace.getNumberHistograms() == expected_num_spectr_ws, "Should have 4 spectra") + + # Since in this test we use the same file for the scatterer and the dark run, we expect + # that the detectors are 0. This is because we subtract bin by bin when using UAMP + all_entries_zero = lambda ws, index : all([0.0 == element for element in ws.dataY(index)]) + + # We only have monitors in our transmission file + for i in [0,1,2,3]: + self.assertFalse(all_entries_zero(transmission_workspace, i), ("Monitor1, Monitor2," + "Monitor3 and Monitor4 entries should not all be 0")) + + def test_that_subtracts_monitors_and_detectors_for_transmission_workspace_with_monitors_and_detectors(self): + # Arrange + use_time = False + use_mean = False + use_mon = False + mon_number = None + run_number = self._get_dark_file() + + settings = [] + setting = self._get_dark_run_settings_object(run_number, use_time, use_mean, use_mon, mon_number) + settings.append(setting) + + use_time2 = False + use_mean2 = False + use_mon2 = True + mon_number2 = [2] + ws_index2 = [1] + run_number2 = self._get_dark_file() + + setting2 = self._get_dark_run_settings_object(run_number2, use_time2, use_mean2, use_mon2, mon_number2) + settings.append(setting2) + + monitor_ids = [1,2,3,4] + trans_ids = monitor_ids + detector_ids = range(1100000, 1100010) + trans_ids.extend(detector_ids) + + # Act + Assert + transmission_workspace = self._do_test_valid_transmission(settings, trans_ids) + + expected_num_spectr_ws = len(trans_ids) # monitors + detectors + self.assertTrue(transmission_workspace.getNumberHistograms() == expected_num_spectr_ws, "Should have the same number of spectra") + + # Since in this test we use the same file for the scatterer and the dark run, we expect + # that the detectors are 0. This is because we subtract bin by bin when using UAMP + all_entries_zero = lambda ws, index : all([0.0 == element for element in ws.dataY(index)]) + + # We only have monitors in our transmission file, monitor 1 should be 0 + for i in [0,2,3]: + self.assertFalse(all_entries_zero(transmission_workspace, i), "Monitor0, Monitor2, Monitor3 entries should not all be 0") + + # Monitor 2 should be set to 0 + for i in ws_index2: + self.assertTrue(all_entries_zero(transmission_workspace, i), "Monitor2 entries should be 0") + + # Detectors should be set to 0 + detector_indices = range(4,14) + for i in detector_indices: + self.assertTrue(all_entries_zero(transmission_workspace, i), "All detectors entries should be 0") + + def test_that_subtracts_monitors_only_for_transmission_workspace_with_monitors_and_detectors(self): + # Arrange + use_time = False + use_mean = False + use_mon = True + mon_number = [2] + ws_index = [1] + run_number = self._get_dark_file() + + settings = [] + setting = self._get_dark_run_settings_object(run_number, use_time, use_mean, use_mon, mon_number) + settings.append(setting) + + monitor_ids = [1,2,3,4] + trans_ids = monitor_ids + detector_ids = range(1100000, 1100010) + trans_ids.extend(detector_ids) + + # Act + Assert + transmission_workspace = self._do_test_valid_transmission(settings, trans_ids) + + expected_num_spectr_ws = len(trans_ids) # monitors + detectors + self.assertTrue(transmission_workspace.getNumberHistograms() == expected_num_spectr_ws, "Should have all spectra") + + # Since in this test we use the same file for the scatterer and the dark run, we expect + # that the detectors are 0. This is because we subtract bin by bin when using UAMP + all_entries_zero = lambda ws, index : all([0.0 == element for element in ws.dataY(index)]) + + # We only have monitors in our transmission file, monitor 1 should be 0 + for i in [0,2,3]: + self.assertFalse(all_entries_zero(transmission_workspace, i), "Monitor0, Monitor2, Monitor3 entries should not all be 0") + + # Monitor 2 should be set to 0 + for i in ws_index: + self.assertTrue(all_entries_zero(transmission_workspace, i), "Monitor2 entries should be 0") + + # Detectors should NOT all be set to 0 + detector_indices = set(range(0, transmission_workspace.getNumberHistograms())) - set(range(0,8)) + all_detectors_zero = True + for i in detector_indices: + all_detectors_zero = all_detectors_zero & all_entries_zero(transmission_workspace, i) + self.assertFalse(all_detectors_zero, "There should be some detectors which are not zero") + + #------- HELPER Methods + def _do_test_valid(self, settings, is_input_event= True, run_number = None) : + # Arrange + dark_run_subtractor = DarkRunSubtraction() + for setting in settings: + dark_run_subtractor.add_setting(setting) + + # Create an actual scatter workspace + scatter_workspace = None + monitor_workspace = None + if is_input_event: + scatter_workspace, monitor_workspace = self._get_sample_workspaces(run_number) + else: + scatter_workspace, monitor_workspace = self._get_sample_workspace_histo() + + # Execute the dark_run_subtractor + try: + start_spec = 9 + end_spec = 20 # Full specturm length + scatter_workspace, monitor_workspace = dark_run_subtractor.execute(scatter_workspace, monitor_workspace, + start_spec, end_spec, is_input_event) + # pylint: disable=bare-except + except: + self.assertFalse(True, "The DarkRunSubtraction executed with an error") + return scatter_workspace, monitor_workspace + + def _do_test_valid_transmission(self, settings, trans_ids): + # Arrange + dark_run_subtractor = DarkRunSubtraction() + for setting in settings: + dark_run_subtractor.add_setting(setting) + + # Create an actual scatter workspace + transmission_workspace = self._get_transmission_workspace(trans_ids) + + # Execute the dark_run_subtractor + try: + transmission_workspace = dark_run_subtractor.execute_transmission(transmission_workspace, trans_ids) + # pylint: disable=bare-except + except: + self.assertFalse(True, "The DarkRunSubtraction executed with an error") + return transmission_workspace + + def _get_dark_file(self): + # Provide an event file from the system test data repo + return "SANS2D00028827_removed_spectra.nxs" + + def _get_sample_workspaces(self, run_number = None): + monitor_ws = None + file_path = None + ws_name = None + sample_ws = None + if run_number is not None: + file_path, ws_name= getFileAndName(run_number) + alg_load = AlgorithmManager.createUnmanaged("LoadNexusProcessed") + alg_load.initialize() + alg_load.setChild(True) + alg_load.setProperty("Filename", file_path) + alg_load.setProperty("EntryNumber", 1) + alg_load.setProperty("OutputWorkspace", ws_name) + alg_load.execute() + sample_ws = alg_load.getProperty("OutputWorkspace").value + else: + file_path, ws_name= getFileAndName(self._get_dark_file()) + alg_load = AlgorithmManager.createUnmanaged("Load") + alg_load.initialize() + alg_load.setChild(True) + alg_load.setProperty("Filename", file_path) + alg_load.setProperty("OutputWorkspace", ws_name) + alg_load.execute() + sample_ws = alg_load.getProperty("OutputWorkspace").value + + # Now get the monitor + monitor_ws = None + if run_number is not None: + monitors_name = ws_name + "_monitors" + alg_load2 = AlgorithmManager.createUnmanaged("LoadNexusProcessed") + alg_load2.initialize() + alg_load2.setChild(True) + alg_load2.setProperty("Filename", file_path) + alg_load2.setProperty("EntryNumber", 2) + alg_load2.setProperty("OutputWorkspace", monitors_name) + alg_load2.execute() + monitor_ws = alg_load2.getProperty("OutputWorkspace").value + else: + # Load the monitor workspace + monitors_name = ws_name + "_monitors" + alg_load_monitors = AlgorithmManager.createUnmanaged("LoadNexusMonitors") + alg_load_monitors.initialize() + alg_load_monitors.setChild(True) + alg_load_monitors.setProperty("Filename", file_path) + alg_load_monitors.setProperty("MonitorsAsEvents", False) + alg_load_monitors.setProperty("OutputWorkspace", monitors_name) + alg_load_monitors.execute() + monitor_ws = alg_load_monitors.getProperty("OutputWorkspace").value + + # Rebin the scatter data to match the monitor binning + rebinned_name = "monitor_rebinned" + alg_rebin = AlgorithmManager.createUnmanaged("RebinToWorkspace") + alg_rebin.initialize() + alg_rebin.setChild(True) + alg_rebin.setProperty("WorkspaceToRebin", sample_ws) + alg_rebin.setProperty("WorkspaceToMatch", monitor_ws) + alg_rebin.setProperty("PreserveEvents", False) + alg_rebin.setProperty("OutputWorkspace", rebinned_name) + alg_rebin.execute() + sample_ws = alg_rebin.getProperty("OutputWorkspace").value + + return sample_ws, monitor_ws + + def _get_sample_workspace_histo(self): + # Load the event workspace and the monitor workspace. Rebin the event to the monitor + # and conjoin them + file_path, ws_name= getFileAndName(self._get_dark_file()) + alg_load = AlgorithmManager.createUnmanaged("LoadEventNexus") + alg_load.initialize() + alg_load.setChild(True) + alg_load.setProperty("Filename", file_path) + alg_load.setProperty("OutputWorkspace", ws_name) + alg_load.execute() + event_ws = alg_load.getProperty("OutputWorkspace").value + + monitor_name = ws_name + "_monitor" + alg_load2 = AlgorithmManager.createUnmanaged("LoadNexusMonitors") + alg_load2.initialize() + alg_load2.setChild(True) + alg_load2.setProperty("Filename", file_path) + alg_load2.setProperty("MonitorsAsEvents", False) + alg_load2.setProperty("OutputWorkspace", monitor_name) + alg_load2.execute() + monitor_ws = alg_load2.getProperty("OutputWorkspace").value + + rebinned_name = ws_name + "_rebinned" + alg_rebin = AlgorithmManager.createUnmanaged("RebinToWorkspace") + alg_rebin.initialize() + alg_rebin.setChild(True) + alg_rebin.setProperty("WorkspaceToRebin", event_ws) + alg_rebin.setProperty("WorkspaceToMatch", monitor_ws) + alg_rebin.setProperty("PreserveEvents", False) + alg_rebin.setProperty("OutputWorkspace", rebinned_name) + alg_rebin.execute() + sample_ws = alg_rebin.getProperty("OutputWorkspace").value + + # We need to create a copy of the sample because it will be deleted/swallowed by + # the conjoin below + cloned_name = ws_name + "_cloned" + alg_rebin = AlgorithmManager.createUnmanaged("CloneWorkspace") + alg_rebin.initialize() + alg_rebin.setChild(True) + alg_rebin.setProperty("InputWorkspace", sample_ws) + alg_rebin.setProperty("OutputWorkspace", cloned_name) + alg_rebin.execute() + sample_ws_copy = alg_rebin.getProperty("OutputWorkspace").value + + alg_conjoined = AlgorithmManager.createUnmanaged("ConjoinWorkspaces") + alg_conjoined.initialize() + alg_conjoined.setChild(True) + alg_conjoined.setProperty("InputWorkspace1", monitor_ws) + alg_conjoined.setProperty("InputWorkspace2", sample_ws_copy) + alg_conjoined.setProperty("CheckOverlapping", True) + alg_conjoined.execute() + monitor_ws = alg_conjoined.getProperty("InputWorkspace1").value + + return sample_ws, monitor_ws + + def _get_transmission_workspace(self, trans_ids): + trans_ws = None + file_path = None + + file_path, ws_name= getFileAndName(self._get_dark_file()) + alg_load = AlgorithmManager.createUnmanaged("LoadEventNexus") + alg_load.initialize() + alg_load.setChild(True) + alg_load.setProperty("Filename", file_path) + alg_load.setProperty("OutputWorkspace", ws_name) + alg_load.execute() + detector = alg_load.getProperty("OutputWorkspace").value + + trans_name = ws_name + "_monitor" + alg_load_monitors = AlgorithmManager.createUnmanaged("LoadNexusMonitors") + alg_load_monitors.initialize() + alg_load_monitors.setChild(True) + alg_load_monitors.setProperty("Filename", file_path) + alg_load_monitors.setProperty("MonitorsAsEvents", False) + alg_load_monitors.setProperty("OutputWorkspace", trans_name) + alg_load_monitors.execute() + monitor = alg_load_monitors.getProperty("OutputWorkspace").value + + rebinned_name = ws_name + "_rebinned" + alg_rebin = AlgorithmManager.createUnmanaged("RebinToWorkspace") + alg_rebin.initialize() + alg_rebin.setChild(True) + alg_rebin.setProperty("WorkspaceToRebin", detector) + alg_rebin.setProperty("WorkspaceToMatch", monitor) + alg_rebin.setProperty("PreserveEvents", False) + alg_rebin.setProperty("OutputWorkspace", rebinned_name) + alg_rebin.execute() + detector = alg_rebin.getProperty("OutputWorkspace").value + + alg_conjoined = AlgorithmManager.createUnmanaged("ConjoinWorkspaces") + alg_conjoined.initialize() + alg_conjoined.setChild(True) + alg_conjoined.setProperty("InputWorkspace1", monitor) + alg_conjoined.setProperty("InputWorkspace2", detector) + alg_conjoined.setProperty("CheckOverlapping", True) + alg_conjoined.execute() + trans_ws = alg_conjoined.getProperty("InputWorkspace1").value + + # And now extract all the required trans ids + extracted_name = "extracted" + alg_extract = AlgorithmManager.createUnmanaged("ExtractSpectra") + alg_extract.initialize() + alg_extract.setChild(True) + alg_extract.setProperty("InputWorkspace", trans_ws) + alg_extract.setProperty("OutputWorkspace", extracted_name) + alg_extract.setProperty("DetectorList", trans_ids) + alg_extract.execute() + return alg_extract.getProperty("OutputWorkspace").value + + def _get_dark_run_settings_object(self, run, time, mean, + use_mon, mon_number): + # This is what would be coming from a parsed user file + return DarkRunSettings(mon = use_mon, + run_number = run, + time = time, + mean = mean, + mon_number = mon_number) + +class DarkRunSubtractionTestStressTest(stresstesting.MantidStressTest): + def __init__(self): + stresstesting.MantidStressTest.__init__(self) + self._success = False + + def runTest(self): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(DarkRunSubtractionTest, 'test')) + runner = unittest.TextTestRunner() + res = runner.run(suite) + if res.wasSuccessful(): + self._success = True + + def requiredMemoryMB(self): + return 2000 + + def validate(self): + return self._success + + +if __name__ == '__main__': + unittest.main() diff --git a/Testing/SystemTests/tests/analysis/SANSLoadersTest.py b/Testing/SystemTests/tests/analysis/SANSLoadersTest.py index f8f081e0add7e8203011f049d8be3bbffcf74ee2..6ff9b02ba102ec639378df942a857dac3135cce3 100644 --- a/Testing/SystemTests/tests/analysis/SANSLoadersTest.py +++ b/Testing/SystemTests/tests/analysis/SANSLoadersTest.py @@ -1,4 +1,4 @@ -#pylint: disable=invalid-name,no-init +#pylint: disable=invalid-name,no-init """ Check the loaders of ISIS SANS reduction. It is created as systemtest because it does take considerable time because it involves loading data. Besides, it uses data that is diff --git a/Testing/SystemTests/tests/analysis/SANSWorkspaceTypeTest.py b/Testing/SystemTests/tests/analysis/SANSWorkspaceTypeTest.py index ac712a997e5f61a9d62875f8587ff6a95f06731a..d9c2e5fec01568b44c33f84d13ca677683db0476 100644 --- a/Testing/SystemTests/tests/analysis/SANSWorkspaceTypeTest.py +++ b/Testing/SystemTests/tests/analysis/SANSWorkspaceTypeTest.py @@ -1,4 +1,4 @@ -#pylint: disable=invalid-name,no-init +#pylint: disable=invalid-name,no-init import stresstesting from mantid.simpleapi import * from SANSUtility import can_load_as_event_workspace diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt index ed23cdbc4dca37bcb9b589337e3e278249f466e8..f66a86653406a9eff68e71c80fe26f7f848dfd48 100644 --- a/scripts/CMakeLists.txt +++ b/scripts/CMakeLists.txt @@ -33,6 +33,8 @@ set ( TEST_PY_FILES test/SANSBatchModeTest.py test/SANSCentreFinderTest.py test/SANSCommandInterfaceTest.py + test/SANSDarkRunCorrectionTest.py + test/SANSUserFileParserTest.py test/SANSUtilityTest.py test/SANSIsisInstrumentTest.py test/SANSReductionStepsUserFileTest.py diff --git a/scripts/SANS/DarkRunCorrection.py b/scripts/SANS/DarkRunCorrection.py new file mode 100644 index 0000000000000000000000000000000000000000..6f4b29ae97b0c3ac242512ed9d20904d9c9618f2 --- /dev/null +++ b/scripts/SANS/DarkRunCorrection.py @@ -0,0 +1,177 @@ +#pylint: disable=invalid-name +from mantid.simpleapi import * + +class DarkRunCorrection(object): + ''' + This class performs the dark run correction for ISIS SANS instruments + ''' + def __init__(self): + super(DarkRunCorrection, self).__init__() + self._normalization_extractor = DarkRunNormalizationExtractor() + + # Should we look at a mean value of the dark count over all pixels. + # Only applicable if the data is uniform + self._use_mean = False + + # Should we use time logs or uamph logs to calculat the normalization ratio. + # In the former case we treat the dark run signal as uniform, ie constant + # (excpt for stat. fluctuations) over time. In the latter case it is treated + # as non-uniform + self._use_time = True + + # Should we use the detectors + self._use_detectors = True + + # Should we use the monitors = False + self._use_monitors = False + + # Which monitor numbers should be used + self._mon_numbers = [] + + def _reset_settings(self): + self._use_mean = False + self._use_time = True + self._use_detectors = True + self._use_monitors = False + self._mon_numbers = [] + + def set_use_mean(self, use_mean): + self._use_mean = use_mean + + def set_use_time(self, use_time): + self._use_time = use_time + + def set_use_detectors(self, use_detectors): + self._use_detectors = use_detectors + + def set_use_monitors(self, use_monitors): + self._use_monitors = use_monitors + + def set_mon_numbers(self, mon_numbers): + if mon_numbers is None: + self._mon_numbers = [] + else: + self._mon_numbers = mon_numbers + + def execute(self, scatter_workspace, dark_run): + ''' + Perform the dark run correction. + @param scatter_workspace: the workspace which needs correcting + @param dark_run: the dark run + ''' + # Get the normalization ratio from the workspaces + normalization_ratio = self._normalization_extractor.extract_normalization(scatter_workspace, + dark_run, self._use_time) + # Run the correction algorithm with the user settings + corrected_ws_name = scatter_workspace.name() + "_dark_workspace_corrected" + alg_dark = AlgorithmManager.createUnmanaged("SANSDarkRunBackgroundCorrection") + alg_dark.initialize() + alg_dark.setChild(True) + alg_dark.setProperty("InputWorkspace", scatter_workspace) + alg_dark.setProperty("DarkRun", dark_run) + alg_dark.setProperty("Mean", self._use_mean) + alg_dark.setProperty("Uniform", self._use_time) # If we use time, then it is uniform + alg_dark.setProperty("NormalizationRatio", normalization_ratio) + alg_dark.setProperty("ApplyToDetectors", self._use_detectors) + alg_dark.setProperty("ApplyToMonitors", self._use_monitors) + alg_dark.setProperty("SelectedMonitors", self._mon_numbers) + alg_dark.setProperty("OutputWorkspace", corrected_ws_name) + alg_dark.execute() + + # Make sure that we forget about the original settings + self._reset_settings() + return alg_dark.getProperty("OutputWorkspace").value + +# pylint: disable=too-few-public-methods +class DarkRunNormalizationExtractor(object): + ''' + Extrats the normalization ratio from the scatter workspace + and the dark run workspace depending. The normalization ratio + can be either calculated as a ratio of good proton charges or + a ratio of measurement times + ''' + def __init__(self): + super(DarkRunNormalizationExtractor, self).__init__() + + def extract_normalization(self, scatter_workspace, dark_run, use_time = True): + ''' + Extract the normalization by either looking at the time duration of the measurement (good_frames) + or by looking at the time of the good charge (good_uah_log) + ''' + if use_time: + normalization = self._extract_normalization_from_time(scatter_workspace, dark_run) + else: + normalization = self._extract_normalization_from_charge(scatter_workspace, dark_run) + return normalization + + def _extract_normalization_from_charge(self, scatter_workspace, dark_run): + ''' + We get the get the normalization ration from the gd_prtn_chrg entries. + @param scatter_workspace: the scatter workspace + @param dark_run: the dark run + @returns a normalization factor for good proton charges + ''' + scatter_proton_charge = self._get_good_proton_charge(scatter_workspace) + dark_proton_charge = self._get_good_proton_charge(dark_run) + return scatter_proton_charge/dark_proton_charge + + def _get_good_proton_charge(self, workspace): + ''' + Get the good proton charge + @param workspace: the workspace from which to extract + @returns the proton charge + ''' + log_entry = "gd_prtn_chrg" + run = workspace.getRun() + if not run.hasProperty(log_entry): + raise RuntimeError("DarkRunCorrection: The workspace does not have a " + log_entry + + "log entry. This is required for calculating the noramlization" + "of the dark run.") + entry = run.getProperty(log_entry) + return entry.value + + def _extract_normalization_from_time(self, scatter_workspace, dark_run): + ''' + Create a normalization ratio based on the duration. + @param scatter_workspace: the scatter workspace + @param dark_run: the dark run + @returns a normalization factor based on good frames + ''' + scatter_time = self._get_duration_for_frames(scatter_workspace) + dark_time = self._get_duration_for_frames(dark_run) + return scatter_time/dark_time + + def _get_duration_for_frames(self, workspace): + ''' + Extract the time duration from the logs. + @param workspace: the workspace to extract from + @returns the duration + ''' + log_entry = "good_frames" + run = workspace.getRun() + if not run.hasProperty(log_entry): + raise RuntimeError("DarkRunCorrection: The workspace does not have a " + log_entry + + "log entry. This is required for calculating the noramlization" + "of the dark run.") + prop = run.getProperty(log_entry) + frame_time = self._get_time_for_frame(workspace) + number_of_frames = self._get_number_of_good_frames(prop) + return frame_time*number_of_frames + + def _get_time_for_frame(self, workspace): + ''' + Get the time of a frame. Look into the first histogram only. + @param workspace: the workspace from which extract the frame time + ''' + return workspace.dataX(0)[-1] - workspace.dataX(0)[0] + + def _get_number_of_good_frames(self, prop): + ''' + Get the number of good frames. + @param prop: the property from which we extract the frames + @returns the number of good frames + ''' + # Since we are dealing with a cummulative sample log, we can extract + # the total number of good frames by looking at the last frame + frames = prop.value + return frames[-1] diff --git a/scripts/SANS/ISISCommandInterface.py b/scripts/SANS/ISISCommandInterface.py index eb18ce9735044f270ec8f1752c78a30ec57dbe3f..6cc8ad0363c1a25f1ade9249188d3ae511fbc89f 100644 --- a/scripts/SANS/ISISCommandInterface.py +++ b/scripts/SANS/ISISCommandInterface.py @@ -19,6 +19,7 @@ import copy from SANSadd2 import * import SANSUtility as su from SANSUtility import deprecated +import SANSUserFileParser as UserFileParser # disable plotting if running outside Mantidplot try: @@ -1668,6 +1669,87 @@ def has_user_file_valid_extension(file_name): print str(is_valid) return is_valid +##################### Accesor functions for BackgroundCorrection +def set_background_correction(run_number, is_time_based, is_mon, is_mean, mon_numbers=None): + ''' + Set a background correction setting. + @param run_number: the run number + @param is_time_based: if it is time-based or uamp-based + @param is_mon: if it is a monitor or a detector + @param is_mean: if it is mean or tof + @param mon_numbers: the monitor numbers of interest or an empty string + ''' + def convert_from_comma_separated_string_to_int_list(input_string): + ''' + Convert from string with comma-separated values to a python int list + @param input_string: the input string + @returns an integer list + @raises RuntimeError: conversion form string to int is not possible + ''' + if input_string is None or len(input_string) == 0: + return None + string_list = su.convert_to_list_of_strings(input_string) + can_convert_to_int = all(su.is_convertible_to_int(element) for element in string_list) + int_list = None + if can_convert_to_int: + int_list = [int(element) for element in string_list] + else: + raise RuntimeError("Cannot convert string list to integer list") + return int_list + mon_numbers_int = convert_from_comma_separated_string_to_int_list(mon_numbers) + + setting = UserFileParser.DarkRunSettings(run_number = run_number, + time = is_time_based, + mean = is_mean, + mon = is_mon, + mon_number = mon_numbers_int) + ReductionSingleton().add_dark_run_setting(setting) + +def get_background_correction(is_time, is_mon, component): + ''' + Gets the background corrections settings for a specific configuration + This can be: time-based + detector, time_based + monitor, + uamp-based + detector, uamp_based + monitor + @param is_time: is it time or uamp based + @param is_mon: is it a monitor or a detector + @param component: string with a component name (need to do this because of the python-C++ interface) + ''' + def convert_from_int_list_to_string(int_list): + ''' + Convert from a python list of integers to a string with comma-separated values + @param int_list: the integer list + @returns the string + ''' + if int_list is None or len(int_list) == 0: + return None + else: + string_list = [str(element) for element in int_list] + return su.convert_from_string_list(string_list) + + setting = ReductionSingleton().get_dark_run_setting(is_time, is_mon) + + value = None + if setting is not None: + if component == "run_number": + value = setting.run_number + elif component == "is_mean": + value = str(setting.mean) + elif component == "is_mon": + value = str(setting.mon) + elif component == "mon_number": + value = convert_from_int_list_to_string(setting.mon_numbers) + else: + pass + print str(value) + return value + +def clear_background_correction(): + ''' + Clears the background correction settings + ''' + ReductionSingleton().clear_dark_run_settings() + + ############################################################################### ######################### Start of Deprecated Code ############################ ############################################################################### diff --git a/scripts/SANS/SANSUserFileParser.py b/scripts/SANS/SANSUserFileParser.py new file mode 100644 index 0000000000000000000000000000000000000000..d39c3312e223f1f7b1be59f16690abeb3c3194e9 --- /dev/null +++ b/scripts/SANS/SANSUserFileParser.py @@ -0,0 +1,226 @@ +#pylint: disable=invalid-name +from collections import namedtuple +import re + +# A settings tuple for dark runs. +DarkRunSettings = namedtuple("DarkRunSettings", "run_number time mean mon mon_number") + +#pylint: disable=too-many-instance-attributes +class BackCommandParser(object): + def __init__(self): + super(BackCommandParser, self).__init__() + self._uniform_key = ['TIME', 'UAMP'] + self._mean_key = ['MEAN', 'TOF'] + self._run_key = ['RUN='] + + # Standard because they all have the same pattern, eg /TIME/MEAN/RUN=1234 + self._first_level_keys_standard = self._uniform_key + + # Special because they are not standard, eg MON/RUN=1234/MEAN/TIME or M4/RUN=1234/MEAN/TIME + self._first_level_keys_special = ['MON', "^M[1-9][0-9]*$"] + + # Evaluation chains + self._standard_chain = [self._first_level_keys_standard, self._mean_key, self._run_key] + self._special_chain = [self._first_level_keys_special, self._run_key, self._uniform_key, self._mean_key] + self._evaluation_chain = None + + # Parse results + self._use_mean = None + self._use_time = None + self._mon = False # This is not being parsed by the standard chain + self._run_number = None + self._mon_number = None + + # Key - method mapping + self._method_map = {''.join(self._first_level_keys_standard): self._evaluate_uniform, + ''.join(self._first_level_keys_special): self._evaluate_mon, + ''.join(self._uniform_key): self._evaluate_uniform, + ''.join(self._mean_key):self._evaluate_mean, + ''.join(self._run_key):self._evaluate_run} + + def _reset_parse_results(self): + self._use_mean = None + self._use_time = None + self._mon = False # This is not being parsed by the standard chain + self._run_number = None + self._mon_number = None + + def _get_method(self, key_list): + return self._method_map[''.join(key_list)] + + def _evaluate_mean(self, argument): + ''' + Evalutes if the argument is either MEAN, TOF or something else. + @param argument: string to investigate + @raise RuntimeError: If the argument cannot be parsed correctly + ''' + if argument == self._mean_key[0]: + self._use_mean = True + elif argument == self._mean_key[1]: + self._use_mean = False + else: + raise RuntimeError("BackCommandParser: Cannot parse the MEAN/TOF value. " + "Read in " + argument +". "+ + "Make sure it is set correctly.") + + def _evaluate_uniform(self, argument): + ''' + Evalutes if the argument is either TIME, UAMP or something else. + @param argument: string to investigate + @raise RuntimeError: If the argument cannot be parsed correctly + ''' + if argument == self._uniform_key[0]: + self._use_time = True + elif argument == self._uniform_key[1]: + self._use_time = False + else: + raise RuntimeError("BackCommandParser: Cannot parse the TIME/UAMP value. " + "Read in " + argument +". "+ + "Make sure it is set correctly.") + + def _evaluate_run(self, argument): + ''' + Evalutes if the argument is RUN= + @param argument: string to investigate + @raise RuntimeError: If the argument cannot be parsed correctly + ''' + if not argument.startswith(self._run_key[0]): + raise RuntimeError("BackCommandParser: Cannot parse the RUN= value. " + "Read in " + argument +". "+ + "Make sure it is set correctly.") + + # Remove the Run= part and take the rest as the run parameter. At this point we cannot + # check if it is a valid run + self._run_number = argument.replace(self._run_key[0], "") + + def _evaluate_mon(self, argument): + ''' + Evaluates which detector to use. At this point the validty of this has already been checkd, so + we can just take it as is. + @param argument: string to investigate + @raise RuntimeError: If the argument cannot be parsed correctly + ''' + if argument == self._first_level_keys_special[0]: # Check if MON + self._mon = True + elif re.match(self._first_level_keys_special[1], argument): + self._mon = True + mon_number = argument.replace("M", "") + self._mon_number=int(mon_number) + else: + raise RuntimeError("BackCommandParser: Cannot parse the MON value. " + "Read in " + argument +". "+ + "Make sure it is set correctly.") + + def can_attempt_to_parse(self, arguments): + ''' + Check if the parameters can be parsed with this class + @param param: a string to be parsed + @returns true if it can be parsed + ''' + # Convert to capital and split the string + to_check = self._prepare_argument(arguments) + + # We expect 3 arguemnts for the standard chain and 4 arguments + # for the special monitor chain + if len(to_check) != 4 and len(to_check) != 3: + return False + + can_parse = False + # Check if part of the first entry of the standard chain + if self._is_parsable_with_standard_chain(to_check): + self._evaluation_chain = self._standard_chain + can_parse = True + elif self._is_parsable_with_special_chain(to_check): + self._evaluation_chain = self._special_chain + can_parse = True + + return can_parse + + def _is_parsable_with_special_chain(self, argument): + ''' + Check if the first entry corresponds to a standard chain, i.e. starting with monitor and followed by run spec. + @param arguments: the string list containing the arguments + ''' + can_parse = False + + if argument[0] == self._first_level_keys_special[0] and argument[1].startswith(self._special_chain[1][0]): + can_parse = True + elif re.match(self._first_level_keys_special[1], argument[0]) and argument[1].startswith(self._special_chain[1][0]): + can_parse = True + + return can_parse + + def _is_parsable_with_standard_chain(self, argument): + ''' + Check if the first entry corresponds to a standard chain, i.e. not starting with a monitor + @param arguments: the string list containing the arguments + ''' + if argument[0] in self._standard_chain[0]: + return True + else: + return False + + def _prepare_argument(self, argumentstring): + ''' + Takes an argument string and returns splits it into a list + @param argumentstring: the input + @returns a list + ''' + # Convert to capital and split the string + split_arguments = argumentstring.split('/') + + # Make everything to upper except for the file name + for index in range(0, len(split_arguments)): + arg = split_arguments[index] + if "=" in arg: + parts = arg.split("=") + # If there are not exactly two elements, then the input is invalid + # We need Run=somerun + if len(parts) != 2: + return [] + new_arg = "RUN=" + parts[1].strip() + split_arguments[index] = new_arg + else: + split_arguments[index] = arg.strip().upper() + return [element.strip() for element in split_arguments] + + def parse_and_set(self, arguments): + ''' + Parse the values of the parameter string and set them on the reducer + @param arguments: the string containing the arguments + @returns an error message if something went wrong or else nothing + ''' + # Parse the arguments. + self._parse(arguments) + + # Now pass the arguments back in a defined format + setting = DarkRunSettings(mon = self._mon, + run_number = self._run_number, + time = self._use_time, + mean = self._use_mean, + mon_number = self._mon_number) + + # Reset the parse results just in case we want to use it again + self._reset_parse_results() + + return setting + + + def _parse(self, arguments): + ''' + Parse the arguments and store the results + @param arguments: the string containing the arguments + @raise RuntimeError: If the argument cannot be parsed correctly + ''' + to_parse = self._prepare_argument(arguments) + + if not self.can_attempt_to_parse(arguments): + raise RuntimeError("BackCommandParser: Cannot parse provided arguments." + "They are not compatible") + + index = 0 + for element in to_parse: + key = self._evaluation_chain[index] + evaluation_method = self._get_method(key) + evaluation_method(element) + index += 1 diff --git a/scripts/SANS/SANSUtility.py b/scripts/SANS/SANSUtility.py index e81108e98aad7fc8d376b949a49dfa4d99460b75..6d6b71686dd48dcee68d02cfe50c3fa62edacebb 100644 --- a/scripts/SANS/SANSUtility.py +++ b/scripts/SANS/SANSUtility.py @@ -17,8 +17,10 @@ import numpy as np sanslog = Logger("SANS") ADDED_EVENT_DATA_TAG = '_added_event_data' -REG_DATA_NAME = '-add' + ADDED_EVENT_DATA_TAG + '[_1-9]*$' -REG_DATA_MONITORS_NAME = '-add_monitors' + ADDED_EVENT_DATA_TAG + '[_1-9]*$' +ADD_TAG = '-add' +ADD_MONITORS_TAG = '-add_monitors' +REG_DATA_NAME = ADD_TAG + ADDED_EVENT_DATA_TAG + '[_1-9]*$' +REG_DATA_MONITORS_NAME = ADD_MONITORS_TAG + ADDED_EVENT_DATA_TAG + '[_1-9]*$' ZERO_ERROR_DEFAULT = 1e6 INCIDENT_MONITOR_TAG = '_incident_monitor' @@ -699,6 +701,7 @@ def get_masked_det_ids(ws): break if det.isMasked(): yield det.getID() + def create_zero_error_free_workspace(input_workspace_name, output_workspace_name): ''' Creates a cloned workspace where all zero-error values have been replaced with a large value @@ -1312,6 +1315,15 @@ def convert_to_string_list(to_convert): output_string = "[" + ','.join("'"+element+"'" for element in string_list) + "]" return output_string +def convert_to_list_of_strings(to_convert): + ''' + Converts a string of comma-separted values to a list of strings + @param to_convert: the string to convert + @returns a list of strings + ''' + values = to_convert.split(",") + return [element.strip() for element in values] + def can_load_as_event_workspace(filename): ''' Check if an file can be loaded into an event workspace @@ -1584,7 +1596,6 @@ def extract_fit_parameters(rAnds): else: fit_mode = "None" return scale_factor, shift_factor, fit_mode - ############################################################################### ######################### Start of Deprecated Code ############################ ############################################################################### diff --git a/scripts/SANS/isis_instrument.py b/scripts/SANS/isis_instrument.py index d8174b575ef7df67c74fdf9dbf496fe93482a822..aa72aadd11997d947f0c1d95f9e2500bd4752f2a 100644 --- a/scripts/SANS/isis_instrument.py +++ b/scripts/SANS/isis_instrument.py @@ -1,4 +1,4 @@ -# pylint: disable=too-many-lines, invalid-name, bare-except +# pylint: disable=too-many-lines, invalid-name, bare-except import math import os import re diff --git a/scripts/SANS/isis_reducer.py b/scripts/SANS/isis_reducer.py index baacbd68961ef775b2298cbe376ae5e8970d83c1..c08058e9134e188a683def0e532876b0fde8370a 100644 --- a/scripts/SANS/isis_reducer.py +++ b/scripts/SANS/isis_reducer.py @@ -217,6 +217,8 @@ class ISISReducer(Reducer): self._slices_def = [] self._slice_index = 0 + # As mentioned above, this is not a reductions step! + def _clean_loaded_data(self): self._sample_run = Sample() @@ -254,6 +256,10 @@ class ISISReducer(Reducer): self.settings = get_settings_object() + # Dark Run Subtraction handler. This is not a step but a utility class + # which gets used during cropping and Tranmission calculation + self.dark_run_subtraction = isis_reduction_steps.DarkRunSubtraction() + def set_instrument(self, configuration): """ @@ -660,6 +666,7 @@ class ISISReducer(Reducer): try: if wk and wk in mtd: DeleteWorkspace(Workspace=wk) + # pylint: disable=bare-except except: #if the workspace can't be deleted this function does nothing pass @@ -699,3 +706,55 @@ class ISISReducer(Reducer): # Update the beam centre finder for the rear self._beam_finder.update_beam_center(centre_pos1, centre_pos2) + def add_dark_run_setting(self, dark_run_setting): + ''' + Adds a dark run setting to the dark run subtraction + @param dark_run_setting: a dark run setting + ''' + self.dark_run_subtraction.add_setting(dark_run_setting) + + def get_dark_run_setting(self, is_time, is_mon): + ''' + Gets one of the four dark run setttings + @param is_time: is it time_based or not + @param is_mon: monitors or not + @returns the requested setting + ''' + setting = None + if is_time and is_mon: + setting = self.dark_run_subtraction.get_time_based_setting_monitors() + elif is_time and not is_mon: + setting = self.dark_run_subtraction.get_time_based_setting_detectors() + elif not is_time and is_mon: + setting = self.dark_run_subtraction.get_uamp_based_setting_monitors() + elif not is_time and not is_mon: + setting = self.dark_run_subtraction.get_uamp_based_setting_detectors() + return setting + + def clear_dark_run_settings(self): + self.dark_run_subtraction.clear_settings() + + def is_based_on_event(self): + ''' + One way to determine if we are dealing with an original event workspace + is if the monitor workspace was loaded separately + @returns true if the input was an event workspace + ''' + was_event = False + if self.is_can(): + sample = self.get_can() + try: + dummy_ws = mtd[can.loader.wksp_name + "_monitors"] + was_event = True + # pylint: disable=bare-except + except: + was_event = False + else: + sample = self.get_sample() + try: + dummy_ws = mtd[sample.loader.wksp_name + "_monitors"] + was_event = True + # pylint: disable=bare-except + except: + was_event = False + return was_event diff --git a/scripts/SANS/isis_reduction_steps.py b/scripts/SANS/isis_reduction_steps.py index 0fc36d5f89c760c9c26790a77762809a5b19d615..3f2f152dad6238536d7cbb1787eb0603a7f05483 100644 --- a/scripts/SANS/isis_reduction_steps.py +++ b/scripts/SANS/isis_reduction_steps.py @@ -25,7 +25,11 @@ from SANSUtility import (GetInstrumentDetails, MaskByBinRange, extract_child_ws_for_added_eventdata, load_monitors_for_multiperiod_event_data, MaskWithCylinder, get_masked_det_ids, get_masked_det_ids_from_mask_file, INCIDENT_MONITOR_TAG, can_load_as_event_workspace, is_convertible_to_float, correct_q_resolution_for_can, - is_valid_user_file_extension) + is_valid_user_file_extension, ADD_TAG, ADD_MONITORS_TAG) +import DarkRunCorrection as DarkCorr + +import SANSUserFileParser as UserFileParser +from collections import namedtuple from reducer_singleton import ReductionStep @@ -1151,7 +1155,6 @@ class Mask_ISIS(ReductionStep): ' rear time mask: ', str(self.time_mask_r) + '\n' + \ ' front time mask: ', str(self.time_mask_f) + '\n' - class LoadSample(LoadRun): """ Handles loading the sample run, this is the main experimental run with data @@ -1188,6 +1191,670 @@ class LoadSample(LoadRun): self.move2ws(0) +class DarkRunSubtraction(object): + ''' + This class handles the subtraction of a dark run from the sample workspace. + The dark run subtraction does not take place and just passes the workspace through + if the parameters are not fully specified. This layer knows about some details of the + ISIS process, eg it knows about the format of Histogram or Event mode reduction and the + shape of added event files. It does not depend on the reducer itself though! + ''' + # The named tuple contains the information for a dark run subtraction for a single run number ( of + # a dark run file) + # The relevant inforamtion is the run number, if we use time or uamp, if we use mean or tof, if we + # apply this to all detectors, if we apply this to monitors and if so to which monitors + DarkRunSubtractionSettings = namedtuple("DarkRunSettings", "run_number time mean detector mon mon_numbers") + + def __init__(self): + super(DarkRunSubtraction, self).__init__() + # This is a list with settings for the dark run subtraction + # Each element in the list will be struct-like and contain + # the relevant information for a dark-run subtraction + self._dark_run_settings = [] + + # We have four types of settings: uamp + det, uamp + mon, time + det, time + mon. + # Richard suggested to limit this for now to these four settings. + self._dark_run_time_detector_setting = None + self._dark_run_uamp_detector_setting = None + self._dark_run_time_monitor_setting = None + self._dark_run_uamp_monitor_setting = None + + # Keeps a hold on the number of histograms in the monitor workspace associated + # with the scatter workspace + + self._monitor_list = None + + def add_setting(self, dark_run_setting): + ''' + We add a dark run setting to our list of settings + @param dark_run_setting + ''' + if not isinstance(dark_run_setting, UserFileParser.DarkRunSettings): + raise RuntimeError("DarkRunSubtraction: The provided settings " + "object is not of type DarkRunSettings") + + # We only add entries where the run number has been specified + if not dark_run_setting.run_number: + return + self._dark_run_settings.append(dark_run_setting) + + def clear_settings(self): + self._dark_run_settings = [] + self._dark_run_time_detector_setting = None + self._dark_run_uamp_detector_setting = None + self._dark_run_time_monitor_setting = None + self._dark_run_uamp_monitor_setting = None + + #pylint: disable=too-many-arguments + def execute(self, workspace, monitor_workspace, start_spec_index, end_spec_index, workspace_was_event): + ''' + Load the dark run. Cropt it to the current detector. Find out what kind of subtraction + to perform and subtract the dark run from the workspace. + @param workspace: the original workspace + @param monitor_workspace: the associated monitor workspace + @param start_spec_index: the start workspace index to consider + @param end_spec_index: the end workspace index to consider + @param workspace_was_event: flag if the original workspace was derived from histo or event + @returns a corrected detector and monitor workspace + ''' + # The workspace signature of the execute method exists to make it an easy step + # to convert to a full-grown reduction step for now we add it when converting + # the data to histogram + if not self.has_dark_runs(): + return workspace + + # Set the number of histograms + self._monitor_list = self._get_monitor_list(monitor_workspace) + + # Get the time-based correction settings for detectors + setting_time_detectors = self.get_time_based_setting_detectors() + # Get the uamp-based correction settings for detectors + setting_uamp_detectors = self.get_uamp_based_setting_detectors() + # Get the time-based correction settings for monitors + setting_time_monitors = self.get_time_based_setting_monitors() + # Get the uamp-based correction settings for monitors + setting_uamp_monitors = self.get_uamp_based_setting_monitors() + + monitor_settings = [setting_time_monitors, setting_uamp_monitors] + detector_settings =[setting_time_detectors, setting_uamp_detectors] + + # Subtract the dark runs for the monitors + for setting in monitor_settings: + if setting is not None: + monitor_workspace = self._execute_dark_run_subtraction_monitors(monitor_workspace, setting, workspace_was_event) + + # Subtract the dark runs for the detectors + for setting in detector_settings: + if setting is not None: + workspace = self._execute_dark_run_subtraction_detectors(workspace, setting, start_spec_index, end_spec_index) + + return workspace, monitor_workspace + + def execute_transmission(self, workspace, trans_ids): + ''' + Performs the dark background correction for transmission and direct workspaces + @param workspace: a transmission workspace (histogram!). We need to have a separate method + for transmission since we the format slightly different to the scattering + workspaces. + @param transmission_ids: a list of transmission spectrum ids + @returns a subtracted transmission workspace + ''' + if not self.has_dark_runs(): + return workspace + + # Set the number of histograms + self._monitor_list = self._get_monitor_list(workspace) + # Get the time-based correction settings for detectors + setting_time_detectors = self.get_time_based_setting_detectors() + # Get the uamp-based correction settings for detectors + setting_uamp_detectors = self.get_uamp_based_setting_detectors() + + # Get the time-based correction settings for monitors + setting_time_monitors = self.get_time_based_setting_monitors() + # Get the uamp-based correction settings for monitors + setting_uamp_monitors = self.get_uamp_based_setting_monitors() + + settings = [setting_time_monitors, setting_uamp_monitors, setting_time_detectors, setting_uamp_detectors] + + for setting in settings: + if setting is not None: + workspace = self._execute_dark_run_subtraction_for_transmission(workspace, setting, trans_ids) + return workspace + + def _execute_dark_run_subtraction_for_transmission(self, workspace, setting, trans_ids): + ''' + @param workspace: the transmission workspace + @param setting: the setting which is to be applied + @param trans_ids: the detector ids which can be found in the transmission workspace + @returns a subtracted transmission workspace + ''' + # Get the name and file path to the dark run + dark_run_name, dark_run_file_path = self._get_dark_run_name_and_path(setting) + + # The transmission contains the monitors and the detectors + dark_run_ws = self._load_dark_run_transmission(workspace, dark_run_name, dark_run_file_path, trans_ids) + + # Subtract the dark run from the workspace + return self._subtract_dark_run(workspace, dark_run_ws, setting) + + def has_dark_runs(self): + ''' + Check if there are any dark run settings which are to be applied + @returns true if there are any dark runs settings which are to be applied + ''' + if not self._dark_run_settings: + return False + else: + return True + + def _load_dark_run_transmission(self, workspace, dark_run_name, dark_run_file_path, trans_ids): + ''' + Loads the dark run in the correct format for transmission correction. The dark run + files will always be Event workspaces, hence we need load the detector and monitor + separately and conjoin them. + @param workspace + @param dark_run_name + @param dark_run_file_path + @param trans_ids: the detector ids which can be found in the transmission workspace + @returns a dark run workspace + ''' + # Load the monitors + monitor = self._load_dark_run_monitors(dark_run_name, dark_run_file_path) + monitor = self._rebin_to_match(workspace, monitor) + + # Load the detectors if the workspace contains detectors at all + contains_detectors = True + if len(self._monitor_list) == workspace.getNumberHistograms(): + contains_detectors = False + + out_ws = monitor + if contains_detectors: + start_spec_index = len(self._monitor_list) + 1 + end_spec_index = workspace.getNumberHistograms() # It already contains the +1 offset + workspace_index_offset = len(self._monitor_list) # Note that this is needed to be consistent + # with non-transmission correction + detector = self._load_workspace(dark_run_name, dark_run_file_path, + start_spec_index,end_spec_index, workspace_index_offset) + detector = self._rebin_to_match(workspace, detector) + # Conjoin the monitors and the detectors + out_ws = self._conjoin_monitor_with_detector_workspace(monitor, detector) + + # Extract the spectra which are present in the transmission workspace + extract_name = workspace.name() + "_extracted" + alg_extract = AlgorithmManager.createUnmanaged("ExtractSpectra") + alg_extract.initialize() + alg_extract.setChild(True) + alg_extract.setProperty("InputWorkspace", out_ws) + alg_extract.setProperty("OutputWorkspace", extract_name) + alg_extract.setProperty("OutputWorkspace", extract_name) + alg_extract.setProperty("DetectorList", trans_ids) + alg_extract.execute() + return alg_extract.getProperty("OutputWorkspace").value + + def _get_monitor_list(self, monitor_workspace): + ''' + This makes use of the fact that the monitor data is always to be found at the front of the data set. + If we are dealing with a monitor_workspace which originates from an event-based workspace, then all + elements should be picked upt. If we are dealing with a monitor which originaates from a histo-based + workspace, then only the first 8 to 10 indices should be found. This knowledge of the workspace layout + helps to speed up things. + @param monitor_workspace: the monitor workspace + @returns a list with monitor spectra + ''' + monitor_indices = [] + for ws_index in range(monitor_workspace.getNumberHistograms()): + try: + det = monitor_workspace.getDetector(ws_index) + except RuntimeError: + # Skip the rest after finding the first spectra with no detectors, + # which is a big speed increase for SANS2D. + break + if det.isMonitor(): + monitor_indices.append(ws_index) + return monitor_indices + + def _execute_dark_run_subtraction_detectors(self, workspace, setting, start_spec_index, end_spec_index): + ''' + Apply one dark run setting to a detector workspace + @param worksapce: the SANS data set with only detector data + @param setting: a dark run settings tuple + @param start_spec_index: the start spec number to consider + @param end_spec_index: the end spec number to consider + ''' + # Get the dark run name and path + dark_run_name, dark_run_file_path = self._get_dark_run_name_and_path(setting) + # Load the dark run workspace is it has not already been loaded + dark_run_ws = self._load_dark_run_detectors(workspace, dark_run_name, dark_run_file_path, + start_spec_index, end_spec_index) + + # Subtract the dark run from the workspace + return self._subtract_dark_run(workspace, dark_run_ws, setting) + + #pylint: disable=too-many-arguments + def _load_dark_run_detectors(self, workspace, dark_run_name, dark_run_file_path, + start_spec_index, end_spec_index): + ''' + Loads a dark run workspace for detector subtraction if it has not already been loaded + @param workspace: the scatter workspace + @param dark_run_name: the name of the dark run workspace + @param dark_run_file_path: the file path to the dark run + @param start_spec_index: the start spec number to consider + @param end_spec_index: the end spec number to consider + @returns a dark run workspace + ''' + # Load the dark run workspace + dark_run = self._load_workspace(dark_run_name, dark_run_file_path, + start_spec_index, end_spec_index, + len(self._monitor_list)) + + # Rebin to match the scatter workspace + return self._rebin_to_match(workspace, dark_run) + + def _execute_dark_run_subtraction_monitors(self, monitor_workspace, setting, workspace_was_event): + ''' + Apply one dark run setting to a monitor workspace + @param monitor_workspace: the monitor data set associated with the scatter data + @param setting: a dark run settings tuple + @param workspace_was_event: flag if the original workspace was derived from histo or event + @returns a monitor for the dark run + ''' + # Get the dark run name and path for the monitor + dark_run_name, dark_run_file_path = self._get_dark_run_name_and_path(setting) + # Load the dark run workspace is it has not already been loaded + monitor_dark_run_ws = self._load_dark_run_monitors(dark_run_name, dark_run_file_path) + + # Check if the original workspace is based on Histo or Event. If it was an event workspace, + # then the monitor workspace of the dark run should match (note that for event workspaces + # the monitor workspace is separate). In the case of histo workspaces the monitor data + # has all the histo data appended. + out_ws = None + if workspace_was_event: + # Rebin to the dark run monitor workspace to the original monitor workspace + monitor_dark_run_ws = self._rebin_to_match(monitor_workspace, monitor_dark_run_ws) + out_ws = self._subtract_dark_run(monitor_workspace, monitor_dark_run_ws, setting) + else: + out_ws = self._get_monitor_workspace_from_original_histo_input(monitor_workspace, monitor_dark_run_ws, setting) + + return out_ws + + def _get_monitor_workspace_from_original_histo_input(self, monitor_workspace, monitor_dark_run_ws, setting): + ''' + Get the monitor workspace from an original histo input. Note that this is just the original input workspace. + We will extract the monitor workspaces, do the subtraction and add it back to the original workspace. + @param monitor_workspace: the monitor workspace (actually the full input) + @param monitor_dark_run_ws: the dark run workspace + @param setting: a dark run settings tuple + @returns the corrected monitor workspace + ''' + # Extract the spectra + monitor_temp_name = monitor_workspace.name() + "_mon_tmp" + alg_extract = AlgorithmManager.createUnmanaged("ExtractSpectra") + alg_extract.initialize() + alg_extract.setChild(True) + alg_extract.setProperty("InputWorkspace", monitor_workspace) + alg_extract.setProperty("OutputWorkspace", monitor_temp_name) + alg_extract.setProperty("WorkspaceIndexList", self._monitor_list) + alg_extract.execute() + ws_mon_tmp = alg_extract.getProperty("OutputWorkspace").value + + # Rebin to match + monitor_dark_run_ws = self._rebin_to_match(ws_mon_tmp, monitor_dark_run_ws) + + # Subtract the two monitor workapces + ws_mon_tmp = self._subtract_dark_run(ws_mon_tmp, monitor_dark_run_ws, setting) + + # Stitch back together + monitor_workspace = self._crop_workspace_at_front(monitor_workspace, len(self._monitor_list)) + return self._conjoin_monitor_with_detector_workspace(ws_mon_tmp, monitor_workspace) + + def _load_dark_run_monitors(self, dark_run_name, dark_run_file_path): + ''' + Loads the monitor dark run workspace. + @param dark_run_name: the name of the dark run workspace + @param dark_run_file_path: the file path to the dark run + @returns a monitor for the dark run + ''' + # This is a bit tricky. There are several possibilities. Of loading the monitor data + # 1. The input is a standard workspace and we have to load via LoadNexusMonitor + # 2. The input is a -add file. Then we need to load the file and extract the monitor of the added data set + monitor_ws = None + monitors_name = dark_run_name + "_monitors" + # Check if the name ends with the -add identifier. Then we know it has to be a group workspace + if ADD_TAG in dark_run_name: + alg_load_monitors = AlgorithmManager.createUnmanaged("LoadNexusProcessed") + alg_load_monitors.initialize() + alg_load_monitors.setChild(True) + alg_load_monitors.setProperty("Filename", dark_run_file_path) + alg_load_monitors.setProperty("EntryNumber", 2) + alg_load_monitors.setProperty("OutputWorkspace", monitors_name) + alg_load_monitors.execute() + monitor_ws = alg_load_monitors.getProperty("OutputWorkspace").value + else: + try: + alg_load_monitors = AlgorithmManager.createUnmanaged("LoadNexusMonitors") + alg_load_monitors.initialize() + alg_load_monitors.setChild(True) + alg_load_monitors.setProperty("Filename", dark_run_file_path) + alg_load_monitors.setProperty("MonitorsAsEvents", False) + alg_load_monitors.setProperty("OutputWorkspace", monitors_name) + alg_load_monitors.execute() + monitor_ws = alg_load_monitors.getProperty("OutputWorkspace").value + except: + raise RuntimeError("DarkRunSubtration: The monitor workspace for the specified dark run " + "file cannot be found or loaded. " + "Please make sure that that it exists in your search directory.") + return monitor_ws + + def _get_dark_run_name_and_path(self, setting): + ''' + @param settings: a dark run settings tuple + @returns a dark run workspace name and the dark run path + @raises RuntimeError: if there is an issue with loading the workspace + ''' + dark_run_ws_name = None + dark_run_file_path = None + try: + dark_run_file_path, dark_run_ws_name = getFileAndName(setting.run_number) + dark_run_file_path = dark_run_file_path.replace("\\", "/") + except: + raise RuntimeError("DarkRunSubtration: The specified dark run file cannot be found or loaded. " + "Please make sure that that it exists in your search directory.") + return dark_run_ws_name, dark_run_file_path + + #pylint: disable=too-many-arguments + def _load_workspace(self, dark_run_ws_name, dark_run_file_path, start_spec_index, end_spec_index, workspace_index_offset): + ''' + Loads the dark run workspace + @param dark_run_file_path: file path to the dark run + @param dark_run_ws_name: the name of the dark run workspace + @param start_spec_index: the start spec number to consider + @param end_spec_index: the end spec number to consider + @param workspace_index_offset: the number of monitors + @returns a dark run + @raises RuntimeError: if there is an issue with loading the workspace + ''' + dark_run_ws = None + if ADD_TAG in dark_run_ws_name: + alg_load = AlgorithmManager.createUnmanaged("LoadNexusProcessed") + alg_load.initialize() + alg_load.setChild(True) + alg_load.setProperty("Filename", dark_run_file_path) + # We specifically grab the first entry here. When the Nexus file is a Group worspace (due + # to added files) then we specifically only want the first workspace. + alg_load.setProperty("EntryNumber", 1) + alg_load.setProperty("OutputWorkspace", dark_run_ws_name) + alg_load.execute() + dark_run_ws= alg_load.getProperty("OutputWorkspace").value + else: + try: + alg_load = AlgorithmManager.createUnmanaged("Load") + alg_load.initialize() + alg_load.setChild(True) + alg_load.setProperty("Filename", dark_run_file_path) + alg_load.setProperty("OutputWorkspace", dark_run_ws_name) + alg_load.execute() + dark_run_ws= alg_load.getProperty("OutputWorkspace").value + except: + raise RuntimeError("DarkRunSubtration: The specified dark run file cannot be found or loaded. " + "Please make sure that that it exists in your search directory.") + + # Crop the workspace if this is required + if dark_run_ws.getNumberHistograms() != (end_spec_index - start_spec_index + 1): + start_ws_index, end_ws_index = self._get_start_and_end_ws_index(start_spec_index, + end_spec_index, + workspace_index_offset) + # Now crop the workspace to the correct size + cropped_name = dark_run_ws_name + "_cropped" + alg_crop = AlgorithmManager.createUnmanaged("CropWorkspace") + alg_crop.initialize() + alg_crop.setChild(True) + alg_crop.setProperty("InputWorkspace", dark_run_ws) + alg_crop.setProperty("OutputWorkspace", cropped_name) + alg_crop.setProperty("StartWorkspaceIndex", start_ws_index) + alg_crop.setProperty("EndWorkspaceIndex", end_ws_index) + alg_crop.execute() + dark_run_ws= alg_crop.getProperty("OutputWorkspace").value + return dark_run_ws + + def _get_start_and_end_ws_index(self,start_spec_index, end_spec_index, workspace_index_offset): + ''' + Get the start and stop index taking into account the monitor offset + in the original combined workspace. The start_ws_index and end_ws_index + are based on workspaces which contain the monitor data. Our loaded workspace + would not contain the monitor data. Also there is an offset of 1 between + the workspace index and the mon spectrum + @param start_spec_index: the start spec index + @param end_spec_index: the end spec index + @param workspace_index_offset: the workspaec index offset due to the monitors + ''' + # Correct for the spec-ws_index offset and for the monitors + start_ws_index = start_spec_index - workspace_index_offset - 1 + end_ws_index = end_spec_index - workspace_index_offset - 1 + return start_ws_index, end_ws_index + + def _rebin_to_match(self, workspace, to_rebin): + ''' + Rebin too match the input workspace + ''' + rebinned_name = to_rebin.name()+"_rebinned" + alg_rebin = AlgorithmManager.createUnmanaged("RebinToWorkspace") + alg_rebin.initialize() + alg_rebin.setChild(True) + alg_rebin.setProperty("WorkspaceToRebin", to_rebin) + alg_rebin.setProperty("WorkspaceToMatch", workspace) + alg_rebin.setProperty("PreserveEvents", False) + alg_rebin.setProperty("OutputWorkspace", rebinned_name) + alg_rebin.execute() + return alg_rebin.getProperty("OutputWorkspace").value + + def _subtract_dark_run(self, workspace, dark_run, setting): + ''' + Subtract the dark run from the SANS workspace + @param worksapce: the SANS data set + @param dark_run: the dark run workspace + @param setting: a dark run settings tuple + ''' + dark_run_correction = DarkCorr.DarkRunCorrection() + dark_run_correction.set_use_mean(setting.mean) + dark_run_correction.set_use_time(setting.time) + dark_run_correction.set_use_detectors(setting.detector) + dark_run_correction.set_use_monitors(setting.mon) + dark_run_correction.set_mon_numbers(setting.mon_numbers) + return dark_run_correction.execute(scatter_workspace = workspace, + dark_run = dark_run) + + def _crop_workspace_at_front(self, workspace, start_ws_index): + ''' + Creates a cropped workspace + @param workspace: the workspace to be cropped + @param start_ws_index: the start index + @returns a workspace which has the front cropped off + ''' + cropped_temp_name = workspace.name() + "_cropped_tmp" + alg_cropped = AlgorithmManager.createUnmanaged("ExtractSpectra") + alg_cropped.initialize() + alg_cropped.setChild(True) + alg_cropped.setProperty("InputWorkspace", workspace) + alg_cropped.setProperty("OutputWorkspace", cropped_temp_name) + alg_cropped.setProperty("StartWorkspaceIndex", start_ws_index) + alg_cropped.execute() + return alg_cropped.getProperty("OutputWorkspace").value + + def _conjoin_monitor_with_detector_workspace(self, monitor, detector): + ''' + Conjoins the monitor data set with the detector data set + @param monitor: the monitor data set + @param detector: the detector data set + @returns a conjoined data set + ''' + alg_conjoined = AlgorithmManager.createUnmanaged("ConjoinWorkspaces") + alg_conjoined.initialize() + alg_conjoined.setChild(True) + alg_conjoined.setProperty("InputWorkspace1", monitor) + alg_conjoined.setProperty("InputWorkspace2", detector) + alg_conjoined.execute() + return alg_conjoined.getProperty("InputWorkspace1").value + + def get_time_based_setting_detectors(self): + ''' + Retrieve the time-based setting for detectors if there is one + @returns the time-based setting or None + ''' + self._evaluate_settings() + return self._dark_run_time_detector_setting + + def get_uamp_based_setting_detectors(self): + ''' + Retrieve the uamp-based setting for detectors if there is one + @returns the uamp-based setting or None + ''' + self._evaluate_settings() + return self._dark_run_uamp_detector_setting + + def get_time_based_setting_monitors(self): + ''' + Retrieve the time-based setting for monitors if there is one + @returns the time-based setting or None + ''' + self._evaluate_settings() + return self._dark_run_time_monitor_setting + + def get_uamp_based_setting_monitors(self): + ''' + Retrieve the uamp-based setting for monitors if there is one + @returns the uamp-based setting or None + ''' + self._evaluate_settings() + return self._dark_run_uamp_monitor_setting + + def _evaluate_settings(self): + ''' + Takes the dark run settings and merges the appropriate files into + a time-based setting and a uamp-based setting each for detectors + and for monitors. + @returns the final settings + @raises RuntimeError: if settings values are inconsistent + ''' + use_mean = [] + use_time = [] + use_mon = [] + mon_number = [] + run_number = [] + + for setting in self._dark_run_settings: + use_mean.append(setting.mean) + use_time.append(setting.time) + use_mon.append(setting.mon) + mon_number.append(setting.mon_number) + run_number.append(setting.run_number) + + # Get the indices with settings which correspond to the individual settings + get_indices = lambda time_flag, mon_flag : [i for i, val in enumerate(use_time) + if use_time[i] == time_flag and use_mon[i] == mon_flag] + indices_time_detector = get_indices(True, False) + indices_time_monitor = get_indices(True, True) + indices_uamp_detector = get_indices(False, False) + indices_uamp_monitor = get_indices(False, True) + + # Check that for each of these settings we only have one run number specified, else raise an error + has_max_one_run_number = lambda indices : len(set([run_number[index] for index in indices])) < 2 + if (not has_max_one_run_number(indices_time_detector) or + not has_max_one_run_number(indices_time_monitor) or + not has_max_one_run_number(indices_uamp_detector) or + not has_max_one_run_number(indices_uamp_monitor) ) : + raise RuntimeError("DarkRunSubtraction: More background correction runs have been specified than are allowed. " + "There can be maximally one run number for each time-based detector, " + "uamp-based detector, time-based monitor and uamp-based monitor settings.\n") + + # Handle detectors + self._dark_run_time_detector_setting = self._get_final_setting_detectors(run_number, use_mean, + use_time, indices_time_detector) + self._dark_run_uamp_detector_setting = self._get_final_setting_detectors(run_number, use_mean, + use_time, indices_uamp_detector) + + # handle monitors + self._dark_run_time_monitor_setting = self._get_final_setting_monitors(run_number, use_mean, + use_time, mon_number, indices_time_monitor) + self._dark_run_uamp_monitor_setting = self._get_final_setting_monitors(run_number, use_mean, + use_time, mon_number, indices_uamp_monitor) + + def _get_final_setting_detectors(self, run_number, use_mean, use_time, indices): + ''' + Get the final settings for detectors + @param run_number: a list of run numbers + @param use_mean: a list of mean flags + @param use_time: a list of time flags + @param indices: a list if indices + @returns a valid setting for detectors or None + @raises RuntimeError: if there is more than one index specified + ''' + # We want to make sure that there is only one index here. It might be that the user + # specified two settings with the same run number. We need to catch this here. + if len(indices) > 1: + raise RuntimeError("DarkRunSubtraction: The setting for detectors can only be specified once.") + + detector_runs = [run_number[index] for index in indices] + detector_mean = [use_mean[index] for index in indices] + detector_time = [use_time[index] for index in indices] + + if len(detector_runs) == 0 or detector_runs[0] == None: + return None + else: + return DarkRunSubtraction.DarkRunSubtractionSettings(run_number = detector_runs[0], + time = detector_time[0], + mean = detector_mean[0], + detector = True, + mon = False, + mon_numbers = None) + #pylint: disable=too-many-arguments + def _get_final_setting_monitors(self, run_number, use_mean, use_time, mon_numbers, indices): + ''' + Get the final settings for monitors + @param run_number: a list of run numbers + @param use_mean: a list of mean flags + @param use_time: a list of time flags + @param indices: a list if indices + @returns a valid setting for detectors or None + @raises RuntimeError: if settings are not consistent + ''' + # Note that we can have several mon settings, e.g several mon numbers etc. + # So we cannot treat this like detectors + monitor_runs = [run_number[index] for index in indices] + monitor_mean = [use_mean[index] for index in indices] + monitor_time = [use_time[index] for index in indices] + monitor_numbers_to_check = [mon_numbers[index] for index in indices] + monitor_mon_numbers = [] + + # If there is a pure MON setting then we take all Monitors into account, this + # overrides the Mx settings. + if len(monitor_numbers_to_check) > 0 and None in monitor_numbers_to_check: + monitor_mon_numbers = None + else: + for index in indices: + if isinstance(mon_numbers[index], list): + monitor_mon_numbers.extend(mon_numbers[index]) + else: + monitor_mon_numbers.append(mon_numbers[index]) + + # Check if the mean value is identical for all entries + are_all_same = lambda a_list : all([a_list[0] == a_list[i] for i in range(0, len(a_list))]) + if len(monitor_mean) > 0: + if not are_all_same(monitor_mean): + raise RuntimeError("DarkRunSubtraction: If several monitors are specified for a certain type " + "of subtraction, it is required to use either all MEAN or all TOF.") + + # If the runs are empty or None then we don't have any settings here + unique_runs = list(set(monitor_runs)) + if len(unique_runs) == 0 or monitor_runs[0] == None: + return None + else: + return DarkRunSubtraction.DarkRunSubtractionSettings(run_number = monitor_runs[0], + time = monitor_time[0], + mean = monitor_mean[0], + detector = False, + mon = True, + mon_numbers = monitor_mon_numbers) + class CropDetBank(ReductionStep): """ Takes the spectra range of the current detector from the instrument object @@ -1207,6 +1874,28 @@ class CropDetBank(ReductionStep): # Get the detector bank that is to be used in this analysis leave the complete workspace reducer.instrument.cur_detector().crop_to_detector(in_wksp, workspace) + # If the workspace requires dark run subtraction for the detectors and monitors, then this will be performed here. + if reducer.dark_run_subtraction.has_dark_runs(): + # Get the spectrum index range for spectra which are part of the current detector + start_spec_num = reducer.instrument.cur_detector().get_first_spec_num() + end_spec_num = reducer.instrument.cur_detector().last_spec_num + + scatter_ws = getWorkspaceReference(workspace) + scatter_name = scatter_ws.name() + + monitor_ws = reducer.get_sample().get_monitor() + monitor_name = monitor_ws.name() + + + # Run the subtraction + was_event_workspace = reducer.is_based_on_event() + scatter_ws, monitor_ws = reducer.dark_run_subtraction.execute(scatter_ws, monitor_ws, + start_spec_num, end_spec_num, + was_event_workspace) + + # We need to replace the workspaces in the ADS + mtd.addOrReplace(scatter_name, scatter_ws) + mtd.addOrReplace(monitor_name, monitor_ws) class NormalizeToMonitor(ReductionStep): """ @@ -1391,7 +2080,7 @@ class TransmissionCalc(ReductionStep): """ Returns true if the can or sample was given and false if just both was used""" return self.fit_settings.has_key('sample::fit_method') or self.fit_settings.has_key('can::fit_method') - def setup_wksp(self, inputWS, inst, wavbining, trans_det_ids): + def setup_wksp(self, inputWS, inst, wavbining, trans_det_ids, reducer): """ Creates a new workspace removing any background from the monitor spectra, converting units and re-bins. If the instrument is LOQ it zeros values between the x-values 19900 and 20500 @@ -1404,6 +2093,7 @@ class TransmissionCalc(ReductionStep): b) a list of detector that make up a region of interest Together these make up all spectra needed to carry out the CalculateTransmission algorithm. + @param reducer: the reducer singleton @return the name of the workspace created """ # the workspace is forked, below is its new name @@ -1414,13 +2104,15 @@ class TransmissionCalc(ReductionStep): # problems if we don't. extract_spectra(mtd[inputWS], trans_det_ids, tmpWS) + # Perform the a dark run background correction if one was specified + self._correct_dark_run_background(reducer, tmpWS, trans_det_ids) + if inst.name() == 'LOQ': RemoveBins(InputWorkspace=tmpWS, OutputWorkspace=tmpWS, XMin=self.loq_removePromptPeakMin, XMax=self.loq_removePromptPeakMax, Interpolation='Linear') tmp = mtd[tmpWS] - # We perform a FlatBackground correction. We do this in two parts. # First we find the workspace indices which correspond to monitors # and perform the correction on these indicies. @@ -1456,7 +2148,6 @@ class TransmissionCalc(ReductionStep): InterpolatingRebin(InputWorkspace=tmpWS, OutputWorkspace=tmpWS, Params=wavbining) else: Rebin(InputWorkspace=tmpWS, OutputWorkspace=tmpWS, Params=wavbining) - return tmpWS def _get_index(self, number): @@ -1602,9 +2293,9 @@ class TransmissionCalc(ReductionStep): # set up the input workspaces trans_tmp_out = self.setup_wksp(trans_raw, reducer.instrument, \ - wavbin, trans_det_ids) + wavbin, trans_det_ids, reducer) direct_tmp_out = self.setup_wksp(direct_raw, reducer.instrument, \ - wavbin, trans_det_ids) + wavbin, trans_det_ids, reducer) # Where a ROI has been specified, it is useful to keep a copy of the # summed ROI spectra around for the scientists to look at, so that they @@ -1706,6 +2397,18 @@ class TransmissionCalc(ReductionStep): resp = 'LOGARITHMIC' return resp + def _correct_dark_run_background(self, reducer, workspace_name, trans_det_ids): + ''' + Subtract the dark run from the transmission workspace + @param reducer: the reducer object + @param workspace_name: the name of the workspace to correct + @param trans_det_ids: the transmission detector ids + ''' + if reducer.dark_run_subtraction.has_dark_runs(): + # We need to grab the workspace from the ADS and place the corrected workspace back into the ADS + trans_ws = mtd[workspace_name] + trans_ws = reducer.dark_run_subtraction.execute_transmission(trans_ws, trans_det_ids) + mtd.addOrReplace(workspace_name, trans_ws) class AbsoluteUnitsISIS(ReductionStep): DEFAULT_SCALING = 100.0 @@ -2404,7 +3107,6 @@ class SliceEvent(ReductionStep): _hist, (_tot_t, tot_c, _part_t, part_c) = slice2histogram(ws_pointer, start, stop, _monitor, binning) self.scale = part_c / tot_c - class BaseBeamFinder(ReductionStep): """ Base beam finder. Holds the position of the beam center @@ -2759,6 +3461,7 @@ class UserFile(ReductionStep): limit_type = blocks[0].lstrip().rstrip() try: rebin_str = limits.split(limit_type)[1] + # pylint: disable=bare-except except: _issueWarning("Incorrectly formatted limit line ignored \"" + limit_line + "\"") return @@ -2984,10 +3687,17 @@ class UserFile(ReductionStep): @param reducer: the object that contains all the settings @return any errors encountered or '' """ + # Check with a BACK/ User file parser if the desired keywords are in here + # else handle in a standard way + back_parser = UserFileParser.BackCommandParser() + if back_parser.can_attempt_to_parse(arguments): + dark_run_setting = back_parser.parse_and_set(arguments) + reducer.add_dark_run_setting(dark_run_setting) + else: # a list of the key words this function can read and the functions it calls in response - keys = ['MON/TIMES', 'M', 'TRANS'] - funcs = [self._read_default_back_region, self._read_back_region, self._read_back_trans_roi] - self._process(keys, funcs, arguments, reducer) + keys = ['MON/TIMES', 'M', 'TRANS'] + funcs = [self._read_default_back_region, self._read_back_region, self._read_back_trans_roi] + self._process(keys, funcs, arguments, reducer) def _read_back_region(self, arguments, reducer): """ @@ -3149,6 +3859,7 @@ class UserFile(ReductionStep): # Check if it is the moderator file name, if so add it and return if arguments[0].startswith('MODERATOR'): + # pylint: disable=bare-except try: reducer.to_Q.set_q_resolution_moderator(file_name=arguments[1]) except: diff --git a/scripts/test/SANSCommandInterfaceTest.py b/scripts/test/SANSCommandInterfaceTest.py index 8fef2e15df0c49ffe71ef06d3d4ad7089ce25e06..7b35ab1f08e524717e1c662124b6bf5b8338ec4e 100644 --- a/scripts/test/SANSCommandInterfaceTest.py +++ b/scripts/test/SANSCommandInterfaceTest.py @@ -229,11 +229,8 @@ class TestEventWorkspaceCheck(unittest.TestCase): self._clean_up(file_name) DeleteWorkspace(ws) - class SANSCommandInterfaceGetAndSetQResolutionSettings(unittest.TestCase): - ''' - Test the input and output mechanims for the QResolution settings - ''' + #Test the input and output mechanims for the QResolution settings def test_full_setup_for_circular_apertures(self): # Arrange command_iface.Clean() @@ -359,7 +356,6 @@ class SANSCommandInterfaceGetAndSetQResolutionSettings(unittest.TestCase): delta_r_expected = delta_r/1000. self.assertEqual(delta_r_stored, delta_r_expected) - class TestMaskFile(unittest.TestCase): def test_throws_for_user_file_with_invalid_extension(self): # Arrange @@ -370,5 +366,39 @@ class TestMaskFile(unittest.TestCase): args = [file_name] self.assertRaises(RuntimeError, command_iface.MaskFile, *args) +class SANSCommandInterfaceGetAndSetBackgroundCorrectionSettings(unittest.TestCase): + def _do_test_correct_setting(self, run_number, is_time, is_mon, is_mean, mon_numbers): + # Assert that settings were set + setting = ReductionSingleton().get_dark_run_setting(is_time, is_mon) + self.assertEquals(setting.run_number, run_number) + self.assertEquals(setting.time, is_time) + self.assertEquals(setting.mean, is_mean) + self.assertEquals(setting.mon, is_mon) + self.assertEquals(setting.mon_numbers, mon_numbers) + + # Assert that other settings are None. Hence set up all combinations and remove the one which + # has been set up earlier + combinations = [[True, True], [True, False], [False, True], [False, False]] + selected_combination = [is_time, is_mon] + combinations.remove(selected_combination) + + for combination in combinations: + self.assertTrue(ReductionSingleton().get_dark_run_setting(combination[0], combination[1]) is None) + + def test_that_correct_setting_can_be_passed_in(self): + # Arrange + run_number = "test12345" + is_time = True + is_mon = True + is_mean = False + mon_numbers= None + command_iface.Clean() + command_iface.LOQ() + # Act + command_iface.set_background_correction(run_number, is_time, + is_mon, is_mean, mon_numbers) + # Assert + self._do_test_correct_setting(run_number, is_time, is_mon, is_mean, mon_numbers) + if __name__ == "__main__": unittest.main() diff --git a/scripts/test/SANSDarkRunCorrectionTest.py b/scripts/test/SANSDarkRunCorrectionTest.py new file mode 100644 index 0000000000000000000000000000000000000000..1232f70927bc2fd4cd1cd56495c2510906609035 --- /dev/null +++ b/scripts/test/SANSDarkRunCorrectionTest.py @@ -0,0 +1,317 @@ +import unittest +import mantid +from mantid.simpleapi import * +from mantid.kernel import DateAndTime +import DarkRunCorrection as dc + +#--- Helper Functions +def add_log(ws, number_of_times, log_name, start_time): + alg_log = AlgorithmManager.create("AddTimeSeriesLog") + alg_log.initialize() + alg_log.setChild(True) + alg_log.setProperty("Workspace", ws) + alg_log.setProperty("Name", log_name) + + # Add the data + if log_name == "good_frames": + convert_to_type = int + else: + convert_to_type = float + + for i in range(0, number_of_times): + date = DateAndTime(start_time) + date += int(i*1e9) # Add nanoseconds + alg_log.setProperty("Time", date.__str__().strip()) + alg_log.setProperty("Value", convert_to_type(i)) + alg_log.execute() + + return alg_log.getProperty("Workspace").value + +def create_sample_workspace_with_log(name, x_start, x_end, bin_width, + log_name, start_time, number_of_times): + CreateSampleWorkspace(OutputWorkspace = name, + NumBanks=1, + BankPixelWidth=1, + XMin=x_start, + XMax = x_end, + BinWidth = bin_width) + ws = mtd[name] + ws = add_log(ws, number_of_times, log_name, start_time) + + # We need the gd_prtn_chrg if "good_uah_log" present + if "good_uah_log" in log_name: + alg = AlgorithmManager.create("AddSampleLog") + alg.initialize() + alg.setChild(True) + alg.setProperty("Workspace", ws) + alg.setProperty("LogName", "gd_prtn_chrg") + alg.setProperty("LogText", str(ws.getRun().getProperty("good_uah_log").value[-1])) + alg.setProperty("LogType", "Number") + alg.execute() + +def create_real_workspace_with_log(name, log_names, start_time, number_of_times): + filename = "LOQ48127np.nxs" + out_ws_name = name + alg_load = AlgorithmManager.create("LoadNexusProcessed") + alg_load.initialize() + alg_load.setChild(True) + alg_load.setProperty("Filename", filename) + alg_load.setProperty("OutputWorkspace", out_ws_name) + alg_load.execute() + ws = alg_load.getProperty("OutputWorkspace").value + + for log_name in log_names: + ws = add_log(ws, number_of_times, log_name, start_time) + + # We need the gd_prtn_chrg if "good_uah_log" present + if "good_uah_log" in log_names: + alg = AlgorithmManager.create("AddSampleLog") + alg.initialize() + alg.setChild(True) + alg.setProperty("Workspace", ws) + alg.setProperty("LogName", "gd_prtn_chrg") + alg.setProperty("LogText", str(ws.getRun().getProperty("good_uah_log").value[-1])) + alg.setProperty("LogType", "Number") + alg.execute() + return ws + +def create_real_event_workspace_with_log(name, log_names, start_time, number_of_times): + filename = "CNCS_7860_event.nxs" + out_ws_name = name + alg_load = AlgorithmManager.create("LoadNexusProcessed") + alg_load.initialize() + alg_load.setChild(True) + alg_load.setProperty("Filename", filename) + alg_load.setProperty("OutputWorkspace", out_ws_name) + alg_load.execute() + ws = alg_load.getProperty("OutputWorkspace").value + + for log_name in log_names: + ws = add_log(ws, number_of_times, log_name, start_time) + return ws + +# ----- TESTS +class DarkRunCorrectionTest(unittest.TestCase): + def _do_test_dark_run_correction(self, log_name, use_mean, use_time, use_detectors = True, + use_monitors = False, mon_number = None, use_real_data = False): + # Arrange + if mon_number is None: + mon_number = [] + + x_start = 0 + x_end = 10 + bin_width = 1 + + start_time_scatter = 0 # in nanoseconds + number_of_times_scatter = 5 # in seconds + + start_time_dark_run = 7 # in nanoseconds + number_of_times_dark_run = 13 # in seconds + + ws_scatter_name = "ws_scatter" + ws_dark_name = "ws_dark" + + ws_scatter = None + ws_dark = None + + # We either load some data or create sample data + if use_real_data: + ws_scatter = create_real_workspace_with_log("scatter", [log_name], + start_time_scatter, number_of_times_scatter) + ws_dark = create_real_workspace_with_log("dark", [log_name], + start_time_dark_run, number_of_times_dark_run) + else: + create_sample_workspace_with_log(ws_scatter_name, x_start, x_end, bin_width, + log_name, start_time_scatter, number_of_times_scatter) + + create_sample_workspace_with_log(ws_dark_name, x_start, x_end, bin_width, + log_name, start_time_dark_run, number_of_times_dark_run) + ws_scatter = mtd[ws_scatter_name] + ws_dark = mtd[ws_dark_name] + + correction = dc.DarkRunCorrection() + correction.set_use_mean(use_mean) + correction.set_use_time(use_time) + correction.set_use_detectors(use_detectors) + correction.set_use_monitors(use_monitors) + correction.set_mon_numbers(mon_number) + + # Act + out_ws = correction.execute(ws_scatter,ws_dark) + + # Assert only that it runs and produces an output workspace + # The algorithm correctness has already been tested in SANSDarkRunBackgroundCorrectionTest + self.assertTrue(out_ws is not None) + + # Clean up + if not use_real_data: + mtd.remove(ws_scatter_name) + mtd.remove(ws_dark_name) + + def test__mean__time__dark_run_correction_runs(self): + use_mean = True + use_time = True + log_name = "good_frames" + self._do_test_dark_run_correction(log_name, use_mean, use_time) + + def test__mean__no_time__dark_run_correction_runs(self): + use_mean = True + use_time = False + log_name = "good_uah_log" + self._do_test_dark_run_correction(log_name, use_mean, use_time) + + def test__no_mean__time__dark_run_correction_runs(self): + use_mean = False + use_time = True + log_name = "good_frames" + self._do_test_dark_run_correction(log_name, use_mean, use_time) + + def test__no_mean__no_time__dark_run_correction_runs(self): + use_mean = False + use_time = False + log_name = "good_uah_log" + self._do_test_dark_run_correction(log_name, use_mean, use_time) + + + ## -- The tests below make sure that monitor and detector settings can be applied + def test__mean__time__all_monitors__no_detectors(self): + use_mean = True + use_time = True + log_name = "good_frames" + use_detectors = False + use_monitors = True + mon_numbers = [] + + self._do_test_dark_run_correction(log_name, use_mean, use_time, use_detectors, + use_monitors, mon_numbers, + use_real_data = True) + + def test__mean__no_time__one_monitor__no_detectors(self): + use_mean = True + use_time = False + log_name = "good_uah_log" + use_detectors = False + use_monitors = True + mon_numbers = [1] + + self._do_test_dark_run_correction(log_name, use_mean, use_time, use_detectors, + use_monitors, mon_numbers, + use_real_data = True) + + def test__no_mean__time__one_monitor__detectors(self): + use_mean = False + use_time = True + log_name = "good_frames" + use_detectors = True + use_monitors = True + mon_numbers = [1] + + self._do_test_dark_run_correction(log_name, use_mean, use_time, use_detectors, + use_monitors, mon_numbers, + use_real_data = True) + + def test_no_mean__no_time__all_monitors__detectors(self): + use_mean = False + use_time = False + log_name = "good_uah_log" + use_detectors = True + use_monitors = True + mon_numbers = [] + + self._do_test_dark_run_correction(log_name, use_mean, use_time, use_detectors, + use_monitors, mon_numbers, + use_real_data = True) + + +class DarkRunNormalizationExtractorTest(unittest.TestCase): + + def _do_test_extraction(self, log_name, use_time): + # Arrange + x_start = 0 + x_end = 10 + bin_width = 1 + + start_time_scatter = 0 # in nanoseconds + number_of_times_scatter = 5 # in seconds + + start_time_dark_run = 7 # in nanoseconds + number_of_times_dark_run = 13 # in seconds + + + ws_scatter_name = "ws_scatter" + create_sample_workspace_with_log(ws_scatter_name, x_start, x_end, bin_width, + log_name, start_time_scatter, number_of_times_scatter) + ws_dark_name = "ws_dark" + create_sample_workspace_with_log(ws_dark_name, x_start, x_end, bin_width, + log_name, start_time_dark_run, number_of_times_dark_run) + ws_scatter = mtd[ws_scatter_name] + ws_dark = mtd[ws_dark_name] + + extractor = dc.DarkRunNormalizationExtractor() + + # Act + ratio = extractor.extract_normalization(ws_scatter, ws_dark, use_time) + + # Assert + if log_name == "good_frames": + time_frame_scatter = ws_scatter.dataX(0)[-1] - ws_scatter.dataX(0)[0] + number_of_frames_scatter = ws_scatter.getRun().getProperty("good_frames").value[-1] + + time_frame_dark = ws_dark.dataX(0)[-1] - ws_dark.dataX(0)[0] + number_of_frames_dark = ws_dark.getRun().getProperty("good_frames").value[-1] + + expected_ratio = float(time_frame_scatter * number_of_frames_scatter)/float(time_frame_dark*number_of_frames_dark) + else: + expected_ratio = ws_scatter.getRun().getProperty("gd_prtn_chrg").value/ws_dark.getRun().getProperty("gd_prtn_chrg").value + + self.assertEqual(ratio, expected_ratio, "Should have the same ratio") + + # Clean up + mtd.remove(ws_scatter_name) + mtd.remove(ws_dark_name) + + def test_good_frames_logs_can_be_extracted(self): + log_name = "good_frames" + use_time = True + + self._do_test_extraction(log_name, use_time) + + def test_uamph_logs_can_be_extracted(self): + log_name = "good_uah_log" + use_time = False + + self._do_test_extraction(log_name, use_time) + + def test_that_throws_runtime_exception_for_missing_log(self): + # Arrange + x_start = 0 + x_end = 10 + bin_width = 1 + log_name = "WRONG LOG NAME" + + start_time_scatter = 0 # in nanoseconds + number_of_times_scatter = 5 # in seconds + + start_time_dark_run = 7 # in nanoseconds + number_of_times_dark_run = 13 # in seconds + + ws_scatter_name = "ws_scatter" + create_sample_workspace_with_log(ws_scatter_name, x_start, x_end, bin_width, + log_name, start_time_scatter, number_of_times_scatter) + ws_dark_name = "ws_dark" + create_sample_workspace_with_log(ws_dark_name, x_start, x_end, bin_width, + log_name, start_time_dark_run, number_of_times_dark_run) + + extractor = dc.DarkRunNormalizationExtractor() + use_time = True + + # Act + Assert + args= [mtd[ws_scatter_name], mtd[ws_dark_name], use_time] + self.assertRaises(RuntimeError, extractor.extract_normalization,*args) + + # Clean up + mtd.remove(ws_scatter_name) + mtd.remove(ws_dark_name) + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/test/SANSReductionStepsUserFileTest.py b/scripts/test/SANSReductionStepsUserFileTest.py index d06952672db11d8ca3569444b84667427d60258c..fb2139936a2e901c8003725f5bcf30d6c0ab3e85 100644 --- a/scripts/test/SANSReductionStepsUserFileTest.py +++ b/scripts/test/SANSReductionStepsUserFileTest.py @@ -1,4 +1,4 @@ -import unittest +import unittest import mantid import isis_instrument as instruments import ISISCommandInterface as command_iface @@ -40,8 +40,6 @@ class SANSReductionStepsUserFileTest(unittest.TestCase): self.assertEqual(None, start_TOF_ROI, 'The start time should not have been set') self.assertEqual(None, end_TOF_ROI, 'The end time should not have been set') - - class MockConvertTOQISISQResolution(object): def __init__(self): super(MockConvertTOQISISQResolution, self).__init__() diff --git a/scripts/test/SANSUserFileParserTest.py b/scripts/test/SANSUserFileParserTest.py new file mode 100644 index 0000000000000000000000000000000000000000..41274e3bed97d9a58fcfcda12f92205f01c293c3 --- /dev/null +++ b/scripts/test/SANSUserFileParserTest.py @@ -0,0 +1,124 @@ +import unittest +import mantid +import SANSUserFileParser as UserFileParser + +class BackCommandParserTest(unittest.TestCase): + def test_can_parse_correctly_initial_command(self): + # Arrange + correct1 = "TImE /sdlf/sdf" # Correct MAIN + correct2 = "UAMp/sdlf/sdf" # Correct HAB + correct3 = "MON/RUN=1234/sdf/sdf" # Correct Mon/RUN= + parser = UserFileParser.BackCommandParser() + + # Act and assert + self.assertTrue(parser.can_attempt_to_parse(correct1)) + self.assertTrue(parser.can_attempt_to_parse(correct2)) + self.assertTrue(parser.can_attempt_to_parse(correct3)) + + def test_cannot_parse_correctly_initial_command(self): + # Arrange + correct1 = "FRoNT=/sdlf/sdf" # Wrong specifier + correct2 = "MON/sdf/sdf/sdf" # No run number + correct3 = "Time/sdf" # Correct first but incorrect length + + parser = UserFileParser.BackCommandParser() + + # Act and assert + self.assertFalse(parser.can_attempt_to_parse(correct1)) + self.assertFalse(parser.can_attempt_to_parse(correct2)) + self.assertFalse(parser.can_attempt_to_parse(correct3)) + + def test_that_can_parse_TIME_MEAN_RUN(self): + argument = "TIME/ mEAN/RuN=SANS2D1111111" + uniform = True + mean = True + run_number ="SANS2D1111111" + is_mon = False + mon_number = 0 + self.do_test_can_parse_correctly(argument, uniform, mean, run_number, is_mon, mon_number) + + def test_that_can_parse_UAMP_TOF_RUN(self): + argument = "Uamp/ToF /Run=2222" + uniform = False + mean = False + run_number ="2222" + is_mon = False + mon_number = None + self.do_test_can_parse_correctly(argument, uniform, mean, run_number, is_mon, mon_number) + + def test_that_can_parse_TIME_MEAN_RUN(self): + argument = "TIME/tof/run=LOQ33333333" + uniform = True + mean = False + run_number ="LOQ33333333" + is_mon = False + mon_number = None + self.do_test_can_parse_correctly(argument, uniform, mean, run_number, is_mon, mon_number) + + def test_that_can_parse_UAMP_MEAN_RUN(self): + argument = " UAMP/mean /RuN=444444444" + uniform = False + mean = True + run_number ="444444444" + is_mon = False + mon_number = None + self.do_test_can_parse_correctly(argument, uniform, mean, run_number, is_mon, mon_number) + + def test_that_can_parse_MON_RUN_TIME_MEAN(self): + argument = "MON/RUN=123124/time/mean" + uniform = True + mean = True + run_number ="123124" + is_mon = True + mon_number = None + self.do_test_can_parse_correctly(argument, uniform, mean, run_number, is_mon, mon_number) + + def test_rejects_bad_first_value(self): + argument = "GUN/RUN=123124/time/mean " + self.do_test_parsing_fails(argument) + + def test_rejects_bad_first_value(self): + argument = "mean/UAMP//RuN=444444444" + self.do_test_parsing_fails(argument) + + def test_rejects_bad_second_value(self): + argument = "UAMP/meanTT/RuN=444444444" + self.do_test_parsing_fails(argument) + + def test_rejects_bad_third_value(self): + argument = "UAMP/mean/RuN 44444" + self.do_test_parsing_fails(argument) + + def test_that_can_pars_M3_RUN_TIME_MEAN(self): + argument = "M3/RUN=123124/time/mean" + uniform = True + mean = True + run_number ="123124" + is_mon = True + mon_number = 3 + self.do_test_can_parse_correctly(argument, uniform, mean, run_number, is_mon, mon_number) + + def do_test_can_parse_correctly(self, arguments, expected_uniform, + expected_mean, expected_run_number, + is_mon, expected_mon_number): + # Arrange + parser = UserFileParser.BackCommandParser() + # Act + result = parser.parse_and_set(arguments) + # Assert + self.assertEquals(result.mean, expected_mean) + self.assertEquals(result.time, expected_uniform) + self.assertEquals(result.mon, is_mon) + self.assertEquals(result.run_number, expected_run_number) + self.assertEquals(result.mon_number, expected_mon_number) + + def do_test_parsing_fails(self, arguments): + # Arrange + parser = UserFileParser.BackCommandParser() + # Act + args = [arguments] + self.assertRaises(RuntimeError, parser.parse_and_set,*args) + + +if __name__ == "__main__": + unittest.main()